I don't know if it's just the type of year that we've had, but the fact that Ben used my idea to tri-state one pin for video to claw the other pin back fills me with immense pride. I feel validated as a mediocre PIC programmer! :)
@@poble Honestly, I don't even know where to start to explain the craziness. Tell me what you already know and ask concrete questions on what you don't, and I'll do my best to answer.
I hate the computer term "memory" because people use it to tell you the RAM or Storage, but don't specify which. I remember a The Price is Right episode, where a contestant asked how much memory the computer had... A 64gb computer hard drive is horrible, but 64gb RAM is good. . I don't mean to rant, but the extremely common thing nowadays, is for people to call the plastic or glass screen protector for their phone, a screensaver. . I also know a few people that will call their desktop computer tower, a modem... Yeah...
This build has been awesome Ben! As a kid of the 80's seeing how you worked through the entire system, from generating an NTSC signal to your code has been inspiring!
As a someone who wrote many drivers for video displays starting in the 80's I really love the use of every bit you can find. Many people won't get that what you are doing is taking full advantage of the processor. Now I am in a position where I interview kids to write software, The interviews used to have lots of bit twiddling questions, but in the last 20 years it has shifted because very few can even understand bitwise operators, and none of them have any concept what a register is, or even how memory works...
It gets frustrating when you write software because you talk to kids who are competent programmers in what you need, but they have only ever operated seven layers or more away from the hardware and it seems like a game to put the most "tools" between what they're writing and the work. The whole concept of simplifying and adding lightness is gone.
BTW. You can put multiple assembly instructions in one __asm__ statement. Doing that makes you absolutely sure that the compiler won't be doing weird things in between __asm__ statements (which it sometimes may do). You separate instructions using (new line) character. And strings in C can be split across multiple lines (to improve readability). You can even tell the compiler which registers you use for input, output and which registers have been modified inside __asm__, so your inline assembly code can actually be safe. There is also 'naked' (I think it's supported on AVR) function attribute that makes it not generate any prolog or epilog, so you can have whole function in pure inline assembly. You can even use __asm__ outside of a function and do all sorts of weird and wonderful stuff this way.
Put all this in an inline function and you'll get something readable without loss of performance. And turn optimization up to 11 of course. Don't underestimate the power of the compiler, Ben!
@@BenHeckHacks It's not always about being clever or not. Sometimes the language you use just has no way of doing what you want. For example there is no built-in way of rotating bits in C. You can only shift. Sometimes, there are CPU register you simply can't access from C. Or you may want piece of code to be cycle exact.
I started to learn c but never followed through i wrote one program which was like a basic calculator n that was it lol but space invaders is way beyond my skill level so this is awesome speshly seeing as it runs on something so small u could inhale it lol
Just to let you know if you see this. As someone who really didn't understand very much of what you say when I first started watching your content. I now understand about half of what you say. For whatever reason the way you explain things helps some of us absorb information that we might have tried studying before. It's kinda like you make the information more real and thus more easily accessible. And your enjoyment of your work mixed with your references (and now super cute cat) keep us engaged. So seriously thanks. I've learned a surprising amount while being very entertained.
I am now onto your second video, and am totally hooked. I'm a 52 year old EE who has been hacking since I was about 12 years old on electronics. Came up programming doing assembly on z80, 8080, 6502... No one today gets how efficiency was so necessary, back in the day, and it is just the way we think. Now-a-days when I'm not hacking with microcontrollers, I am doing a high paying job developing advancing things for business with the luxury of big ARM microcontrollers with lots of memory, an RTOS, and object oriented C. This video really made me smile knowing I'm not the only one who can squeeze every ounce out of one of these things. The 25 year old engineers I work with will never get it! Great entertainment value too!
I really love these kind of projects and also really miss the sport of squeezing every byte out of a project. Most people use even an ESP32 just for a couple of blinking LED's
That man is just a therapy replacement for depression, you always make me smile from ears to ears in those hard times, such passion and knowledge, a national treasure of a man, thanks for sharing !
I can't even begin to tell you how much I enjoy your videos Ben. You pour your heart into these videos and genuinely do a good job making them. I hope you continue to make these videos because I wouldn't know what to do without them :)
Trick for the future: you can #define a variable to use a register or pin, that way you don't have to refactor the code when trying out things. Such as "#define hPos EICR0", then just replace the last part with whatever is on the menu. You can even do fancier stuff such as "#define isBla (REGISTER & 2)", making it easier to switch bits. I'm still surprised that a full PAL game can run in that amount of RAM.
Just as a quick clarification, define doesn't create variables or change pin names. It's a C preprocessor command that replaces the code with whatever was defined. So #define max_value 10 will literally replace every instance of max_value in the code with '10' before compilation. This plus static function definitions, which does something similar but for functions is excellent for writing low memory use code, but can make debugging a nightmare, because what gets compiled is different from what is written.
One reason for the unstable lines is also the fact the while loop doesn't test for a new line start at same cycles. Sometimes the counter has already counted to 1 or 2 before the new line flag is checked.
man u teased us with the posts of the updates and wen i got off work i looked and you had finished the video this really made my shity day at a dirty factory much much better
Man, you are crazy! Such an amazing game, with animation and sound, all on this tiny chip?! With just 4 data pins?! And almost no memory?! Man, I'm glad to live at the same time as you! Can't wait for other projects.
If he can do this with a couple bytes then surely with a top spec super quantum computer and the neural web thing thats being developed (its like a network or web of wires that goes under your hair so they can i-o read and write directly to the human brain) if they gave this guy access to the newest tech im sure he could invent the matrix with it :) has anyone else seen what im talking about yet? I found out about it the othernday its a web of wires goes under your skin under your hair so they can read and write directly to the brain? Seems a bit dodgy because if ur brain says its real then u hav no way of arguing so it could be very dangerous but im sure it womt be long before they are implanting this into soldiers and then the general public pretty sure it wont be long before its mainstream and peoples phones are built into themselves
Having now found the same compiler version as Ben used, I've found I can save 22 bytes of program space by declaring the "gameOver" and "drawPF" functions as static, allowing the compiler to "inline" those functions. This should also reduce stack usage, allowing more valuable RAM to be used. "drawPF" uses four stack bytes, which are saved otherwise.
cackling at the idea of Tony Soprano discovering the power of mathematics. these videos were incredibly fascinating, from someone with no experience doing this kind of extremely low-resources / low-level programming. there's so much creativity involved in achieving things with those restraints!
Why would someone watch the video without understanding it? It seems pretty obvious. Because it's interesting to learn a little bit about game dev, or programing on a very limited platform. While I understand some of this I find your whole thought process and the dev process fascinating
One of these days I'm gonna work in a Terminator Dark Fate tangent, where I discuss how it completely missed the point of why women liked the Terminator movies to begin with.
@@BenHeckHacks I'd like to hear that tangent. I heard Dark Fate was good (perhaps the best one since 2), I didn't watch it because I didn't like how they killed off John Conner
As someone who does pretty much all my projects in avr c, I learned alot of tricks from watching this series, now you just need to make a cool themed case for it
It was like watching Ustad Zakir Husain of microcontrollers do his thing. loved your confidence, energy and the atmosphere you make. Like said UStad Zakir Husain.
Ben, I love the way you embrace your nerdiness. You seriously just badly sang the theme to Math Lords and you don't even care. You are truly an inspiration to all nerds.
Maybe minor feedback, but it would have been nice to use #define aliases for the registers you were using untraditionally. I appreciate seeing you make the most of the AVR but after a certain point it would have helped to have more readable names.
Also a small related nitpick: defines or const static variables for various constants/magic-numbers. Iirc you can probably even use a special compiler built-in keyword to create aliases for registers or specific address locations so you can still have a proper variable name but give it a fixed location. May make debugging easier.
man this got so cool, imagine how much better it could get if ben actually spent like 1 month only optimizing the code to implement more things, that would be awesome, but now it's up for the community to elaborate
I dont know why i have never found this content. omg. this is the best. i have a co worker who builds circuits like crazy circuits just like this. i always want to sit and watch and listen to him as he curses and constructs a monstrosity. you and him probably know each other. but anyway great content and i can't wait to watch and listen :D
I think it'd save some space/CPU time if the state machine was restructured so you just had one densely cased switch, and checked the scanline counter to change to the next state. The compiler can turn the switch into a compact jump table if the cases are dense but when it's spanning 131 cases it either has to use a jump table with a lot of empty space or in this case it's probably doing something else like a balanced decision tree. Your code/state machine can take advantage of the fact that the scanline counter always increases throughout the frame, so you only have to check for changes from one state to the direct next one.
@@BenHeckHacks What I have in mind is a dense switch on a state value (0 to n, no gaps) where each case includes one if (no else) to change the state based on the scanline counter. I don't think the sort of switch you're doing now would be more efficient in either space or speed.
@@BenHeckHacks I don't know if you were running with any optimizations, but gcc-avr will convert if/then trees/stacks into jump tables or decision trees on as low as -O2. You can see it in the simulator after compilation.
Just in case this isn't obvious to anyone... Don't feel bad if you can't program as easy as this. He's not actually writing the code here, he's copying it from where he already wrote it and probably spent a whole lot of time and pasting it in as he's explaining it. Don't feel bad you can't write code this fast or this quickly or easily. There's a lot more work put in behind this than is obvious in the video. -- someone who's literally done this kind of thing for 20 years and I still can't do it as fast as what he's showing on the screen here. Lol
Yup that's what I did! Notepad++ on the other monitor. Make sure the basic concepts work then explain them - though the issues with sound and low memory were pretty much "real time"
It's amazing. I have watched the two parts for the second time. I love those tricks. Also, I'm wondering if writing everything in assembly directly would reduce the space constraints a bit 😂
Hey there neighbor. I stumbled upon your channel a few weeks ago and love your work. I live in Eau Claire area. After this pandemic is over, maybe we could meet up some time. I am pretty big into printers and electronics myself, although not quite as knowledgeable as you in actually building boards and such. Happy holidays.
I have to say, that's fucking AMAZING!!! I would buy your contraption for 100 bucks just because is so cool seeing you making the code and all that hard work. Would be nice to put it to auction. Or to exchange with other makers, other cool stuff!
You could use input parameters, something like __asm__ __volatile__("out 0x01, %0"::"r"(theData)); So that you don't have to chase down the specific register that was used to pass theData into drawSprite(), especially if it happens to change all of a sudden. The compiler will take care of it all for you. 😄 Edited some more: OUT can only read from registers, so removed the m constraint
Sorry, should be % instead of $. If the compiler is gcc, check out the details for "extended assembly". Takes a little getting used to, but nice and powerful for mixing raw assembly inline with C. :-) edit: and apparently it is better to use "mr" in the template so that the compiler can choose the best way to pass the argument - in "m"emory or in a "r"egister.
@@TomStorey96 In this specific case it has to be the "r" constraint since the out instruction can not read RAM. The "mr" thing is probably not useful on AVR or other RISCy instruction sets.
@@mcg6762 Cool. I dont really have any experience with ATMEL micros. My head is a bit buried in MIPS and m68k in recent times, so Im kind of thinking everything can be just as flexible in terms of sources and destinations. :) I wonder if the compiler will just ignore the m constraint, if it knows that the instruction cant use memory as a source/dest.
@@TomStorey96 No, the compiler doesn't actually parse the inline assembler. It just substitutes the necessary parameters and let's the assembler deal with it. So you would get an error message from the assembler if it tried to pass in a memory reference.
Ok when you are struggling this hard to make space invaders work, something the atari 2600 had virtually no problems with, I think it would be fair to maybe double the horsepower and make the almost tinniest console imaginable. We will all still be impressed.
@@BenHeckHacks Sent me down a little rabbit hole of C semantics. Looks like it's a bit of history that means all case labels must be followed by a statement not a declaration and the semicolon just acts as an empty statement. Another way to do this is to wrap it in curly brackets as this acts as a compound statement which is the way I'm used to. Either way it's an interesting tidbit. Cheers and thanks for the great vid
@@alessi4249 Yeah, the curly braces trick works in pure C as well. The semicolon trick only works using a C++ compiler. Or did they fix that in C11 (variable declarations mixed with code)?
The full changing of registers is a big deal in big CPUs. The main thing "Hyper-Threading" does is provide additional register infrastructure to minimise the impact of this. That's why it provides this much real world performance with so little price increase. IBM does 4 to 8 threads per real core. Because this is a very big deal on most systems. One assembler instructor threw around a figure of about 1000 cycles delay(which in my opinion sounds about reasonable considering the necessary steps and memory delays in general, its probably less in modern architectures as the memory controller was moved into the CPU in AMD and Intel CPUs about 15[?] or so years ago) to access memory. But i assume modenr CPUs use integrated memory, the cache for example to decrease that number somewhat.
@Christopher Grant Wouldn't surprise me. I mean at those speeds size on this scale already matters. I also assumed that RAM access in the uC would be faster simply due to it being this much closer. I mean the whole thing is significantly smaller then a desktop CPU module. Which are partially this big to distribute heat and not kill themselves with their own heat output. Oh, i just did the rough numbers, you are probably right. at 8mhz the signal can travel over _20 meters_ between cycles. So i think your theory of memory access in one instruction is not that outlandish (in uC, at the very least the ones clocked in lower mhz region. Like this one.). I did not even consider that. Good thing i ony talked about much faster units. Because over 1ghz the travel distance already shrinks below 20 centimeters. The speed difference might really matter a lot here. So we can see thermal limits again. 3Ghz shrink it down below 7 centimeters. So it's already getting tricky even with a direct connection and perfect sync. Which again we don have worry about on a uC the RAM might very well be the equivalent of the L1 cache as you said. tl;dr: As i understand them the physics support your estimate. Well, as long as the required circuitry is shorter then 20m to which i have no clue. But that sounds like a lot considering how small transistors are. Maybe someone who desings processors stumbles over this. Or at least soemeone with more transistor logic experience.
@Christopher Grant Just realised: The implication of my text in the brackets: The memory might be the(/one) limiting factor for the clock speed of the attiny. That would be another point for your theory. => Higher speeds might desync due to travel time and therefore break the reliability. Because of the relatively simple design of the chip. Really interested on whether that might be the case.
So is hyper threading good im sure i see a cheap laptop advwrtised the other day and it didnt have it i was kinda shocked but i dont know what it does so thought it might have been outdated or something but if its still useful then i guess it was just really budget?
Per the "I don't understand what he's saying but I watches the whole thing" comments. To me its like watching a craftsman build something and listening to an interesting person. It sure beats listening to silence as I work all by myself.
I think title would benefit of at least a subtitle, this is not only making a console, this is VideoGame Making 101 and many people would miss the video as it stands now when searching for videogame coding tutorials. Amazing video by the way, thanks a lot Ben!
"you're tearing me apart Lisa!" And I absolutely remember carmen san diego. Haha. I think I would have opted for #define rather than refactor in this scenario... Just to retain useful variable names. ;)
36:59 this is the part where the bits break down, everybody's tense no one makes a sound also this is brutal, like going backwards from high level (my exp) down to C ha still is a fun challenge to work with limited resources 45:20 ha 46:30 the song reminds me of "making your way in the world today" from Adventure Time though not original source 1:04:05 lol IT'S ALIVE!!! I give props making long videos like this especially the code part as that's a lot of stuff to sift through and arrange to be entertaining to viewers 1:11:30 haha 1:16:47 it's one of those things you put in your garage to keep the critters away
You know your stuff regarding this beyond me and I have been programming professionally since 1986 and C++ since 1993 but I do have a suggestion for readability. make that __asm__ __volatile__ ("nop"); a C macro named something like NOP. You are using it enough to be worthwhile at this point. When I mentor programmers I try to encourage them to avoid too much cut and paste such to avoid now many mistakes your replicate or how much code you have to change to change a behavior. For those special registers where you repurposed some of the bits are they actually only storing the bits that have meaning or do they have a full set of bits with some of them being unused? If they are just unused you could really store a lot of bits across all of them.
Some of the registers that only have a few bits (like prescalers) have a "virtual" machine word, so you'll see 8 bits. However, some of the bits are tied high or low and if you were to try and set them, which Ben does here, because you can only set full words as the smallest unit, it'll succeed, but those unset-able bits will just remain their constant value.
It would be interesting to see what a demoscene coder could do with one of these. I have already seen some pretty impressive demos on ATMEGA chips and 128 byte intros are popular these days. This might be tricky but you never know...
My comments on the video: That is genius .... using all those reserved registers :D Ben, Hollyhood would like a word with you. The poor cat... also headphone users... specially younger ones, that thing was throwing some high pitch audio... Yep, those registers are special for a reason... That is a lot of code optimizations :) That is an hot glue mess :p ... no pretty ergonomic container to hold it 'safely'? Good video, educative and entertaining ... have like :D
2:10 that looks like you could get away without the ifs even, saving you some code size, since you do the same in both branches: set portb&ddrb, wait, set them again. so you could make the values you set them to the result of a multiplication with `SMCR&0x02!=0`.
Interrupts are not nearly as inefficient as you imply. The routine only saves what you want, it WILL save a return address to jump to after the routine. It is likely way faster than an if() statement. Saving more of the registers is up to the user. The compiler is amazingly efficient at minimizing unnecessary actions but will obviously save more than you would if you wrote you ISR in assembly.
Yeah, interrupts obviously have to switch context and push/pop all registers which the interrupts would otherwise overwrite. While the interrupt processing in the CPU is nearly free, the context switching may not necessarily be so.
@@BenHeckHacks This is a dumb hack, but I did it before in a project. If you keep all of your variables completely global, pass nothing around, the only thing that would get push'd is the PC on an ISR.
The concern about if statements in optimization has to do with invalidating the instruction cache and purging the pipeline. This thing probably has neither, so there's probably no reason to avoid if statements.
Don't use the ATtiny25/45/85 in the future, use the ATtiny13! It has the same 120 instructions as the ATtiny25/45/85, and twice the amount of RAM compared to ATtiny10.
How about doing a 'dual-processor' project? using two in sync / communicating - or make 'discreet chips' using several of them? they're so cheap! you could use single ones as gates if you wanted to :P a little socketed mainboard - a 'video chip' and a 'cpu' it would be so beautiful! (could you use one as 'RAM' using a bank switching technique? 4 pins of two micro-controllers (splitting+doubling memory), using the other pins to switch the bank? well I guess 8 of them for address pins? perhaps use the flash memory as ROM for graphics/code too? - you could use one pin to sync and do a kind of time-slot/auto-increment or delay line access to save on address pins)
@@BenHeckHacks - Mr Ben, Sir - whatever you choose to share? will be a pleasure to learn (or a pleasure to fool myself into thinking I'm starting to understand) :) You're right up there with John Carmack in my eyes. The ATTINY game console makes me feel like I'm seeing Doom again for the first time - easily better than 2077
For a moment i thought ben eater has got a 2nd channel thats just for funn.. Awesome videos btw i love this type of content. I hope you live longer than me
I was tickled at your suggestion to use the AVR Studio 'simulator' for debugging, (since I thought of this quite some time ago.) Another approach for debugging is to mostly develop the app on a 328p, which is easier to work with, and does have hardware debugging available. Atmel did a great job at keeping as much compatibility as possible. So, provided you pay close attention to the subtle differences between the instruction sets, peripherals, general purpose registers etc., you can then port your code over with minimal changes once you're happy with it.
Why not #define variables to be registers instead of refactoring into a nameless blob? Watching this is kind of like watching “Junkyard Wars” for programming ;)
Okay so this is something I've struggled with for years. If you define that it makes your code more legible but it becomes easy to forget what that actually is. And we know now that if we set certain bits there, we will end up causing unwanted behavior. So the question is do we call it what it actually is or do we rename it in an effort to make it easier to read the code? in this case the whole application is so small there is a good argument to just put a comment next to the areas you need one and remind yourself what you're working with...
@@TravisFabel Quite a symble fix for that. Just mark the number of bits. Two more characters is not going to make it unreadable, and that's really all you need to remember in a case like this. In other words "#define _"
Me: Look at all those neat registers reserved for controlling stuff.
Ben: That's free real estate.
I don't know if it's just the type of year that we've had, but the fact that Ben used my idea to tri-state one pin for video to claw the other pin back fills me with immense pride. I feel validated as a mediocre PIC programmer! :)
Yes! It had a few limitations but was good enough for this tiny thing!
I think this is the most appropriate use of "Memory is RAM" ever said!
Spotted the TERF! (Just kidding.)
@@ropersonline ????
@@poble Honestly, I don't even know where to start to explain the craziness. Tell me what you already know and ask concrete questions on what you don't, and I'll do my best to answer.
I hate the computer term "memory" because people use it to tell you the RAM or Storage, but don't specify which. I remember a The Price is Right episode, where a contestant asked how much memory the computer had... A 64gb computer hard drive is horrible, but 64gb RAM is good.
.
I don't mean to rant, but the extremely common thing nowadays, is for people to call the plastic or glass screen protector for their phone, a screensaver.
.
I also know a few people that will call their desktop computer tower, a modem... Yeah...
@@FusionDeveloper Memory always need RAM to me. I say storage when I'm talking about the harddrive.
This build has been awesome Ben! As a kid of the 80's seeing how you worked through the entire system, from generating an NTSC signal to your code has been inspiring!
As a someone who wrote many drivers for video displays starting in the 80's I really love the use of every bit you can find. Many people won't get that what you are doing is taking full advantage of the processor. Now I am in a position where I interview kids to write software, The interviews used to have lots of bit twiddling questions, but in the last 20 years it has shifted because very few can even understand bitwise operators, and none of them have any concept what a register is, or even how memory works...
It gets frustrating when you write software because you talk to kids who are competent programmers in what you need, but they have only ever operated seven layers or more away from the hardware and it seems like a game to put the most "tools" between what they're writing and the work.
The whole concept of simplifying and adding lightness is gone.
Defo thefuture is very backwards i cant belive people buy chromebooks and stuff with less ghz and ram than my pc had 20 years ago its crazy
says, "I dont know what im doing" then proceeds to build a game console in a grain of rice.
Dudes a genius haha
BTW. You can put multiple assembly instructions in one __asm__ statement. Doing that makes you absolutely sure that the compiler won't be doing weird things in between __asm__ statements (which it sometimes may do). You separate instructions using
(new line) character. And strings in C can be split across multiple lines (to improve readability). You can even tell the compiler which registers you use for input, output and which registers have been modified inside __asm__, so your inline assembly code can actually be safe. There is also 'naked' (I think it's supported on AVR) function attribute that makes it not generate any prolog or epilog, so you can have whole function in pure inline assembly. You can even use __asm__ outside of a function and do all sorts of weird and wonderful stuff this way.
Put all this in an inline function and you'll get something readable without loss of performance. And turn optimization up to 11 of course. Don't underestimate the power of the compiler, Ben!
Awesome tips, thanks! And yes, I know the compiler is more clever than I am haha.
@@BenHeckHacks It's not always about being clever or not. Sometimes the language you use just has no way of doing what you want. For example there is no built-in way of rotating bits in C. You can only shift. Sometimes, there are CPU register you simply can't access from C. Or you may want piece of code to be cycle exact.
the idea of using hardware control registers that actually do stuff as ram horrifies me no end
Who else is watching this with no idea of what is happening with the coding stuff but still very much enjoying the process?
I started to learn c but never followed through i wrote one program which was like a basic calculator n that was it lol but space invaders is way beyond my skill level so this is awesome speshly seeing as it runs on something so small u could inhale it lol
Just to let you know if you see this. As someone who really didn't understand very much of what you say when I first started watching your content. I now understand about half of what you say. For whatever reason the way you explain things helps some of us absorb information that we might have tried studying before. It's kinda like you make the information more real and thus more easily accessible. And your enjoyment of your work mixed with your references (and now super cute cat) keep us engaged. So seriously thanks. I've learned a surprising amount while being very entertained.
I am now onto your second video, and am totally hooked. I'm a 52 year old EE who has been hacking since I was about 12 years old on electronics. Came up programming doing assembly on z80, 8080, 6502... No one today gets how efficiency was so necessary, back in the day, and it is just the way we think. Now-a-days when I'm not hacking with microcontrollers, I am doing a high paying job developing advancing things for business with the luxury of big ARM microcontrollers with lots of memory, an RTOS, and object oriented C. This video really made me smile knowing I'm not the only one who can squeeze every ounce out of one of these things. The 25 year old engineers I work with will never get it! Great entertainment value too!
Bro, those graphical bugs are still more stable than the ones in cyberpunk
I really love these kind of projects and also really miss the sport of squeezing every byte out of a project.
Most people use even an ESP32 just for a couple of blinking LED's
Me: Ok, well this almost useless plugin for $application is a 700 Megabyte download.
Ben: 3 WHOLE BITS, NICE
Any register: is unused
Ben: It's a free real estate!
Manifest Destiny in the register file, lol.
This is a perfect example of art inspired through limitations. Constraints breed creative solutions.
That man is just a therapy replacement for depression, you always make me smile from ears to ears in those hard times, such passion and knowledge, a national treasure of a man, thanks for sharing !
I can't even begin to tell you how much I enjoy your videos Ben.
You pour your heart into these videos and genuinely do a good job making them. I hope you continue to make these videos because I wouldn't know what to do without them :)
Trick for the future: you can #define a variable to use a register or pin, that way you don't have to refactor the code when trying out things. Such as "#define hPos EICR0", then just replace the last part with whatever is on the menu. You can even do fancier stuff such as "#define isBla (REGISTER & 2)", making it easier to switch bits. I'm still surprised that a full PAL game can run in that amount of RAM.
I agree, but I wanted to reinforce the idea I was using registers.
Just as a quick clarification, define doesn't create variables or change pin names. It's a C preprocessor command that replaces the code with whatever was defined. So #define max_value 10 will literally replace every instance of max_value in the code with '10' before compilation. This plus static function definitions, which does something similar but for functions is excellent for writing low memory use code, but can make debugging a nightmare, because what gets compiled is different from what is written.
Studying electrical engineering, sometimes struggle to stay motivated and your ability is truly inspiring. Thanks for all the content!!
One reason for the unstable lines is also the fact the while loop doesn't test for a new line start at same cycles. Sometimes the counter has already counted to 1 or 2 before the new line flag is checked.
man u teased us with the posts of the updates and wen i got off work i looked and you had finished the video this really made my shity day at a dirty factory much much better
Man, you are crazy! Such an amazing game, with animation and sound, all on this tiny chip?! With just 4 data pins?! And almost no memory?! Man, I'm glad to live at the same time as you! Can't wait for other projects.
If he can do this with a couple bytes then surely with a top spec super quantum computer and the neural web thing thats being developed (its like a network or web of wires that goes under your hair so they can i-o read and write directly to the human brain) if they gave this guy access to the newest tech im sure he could invent the matrix with it :) has anyone else seen what im talking about yet? I found out about it the othernday its a web of wires goes under your skin under your hair so they can read and write directly to the brain? Seems a bit dodgy because if ur brain says its real then u hav no way of arguing so it could be very dangerous but im sure it womt be long before they are implanting this into soldiers and then the general public pretty sure it wont be long before its mainstream and peoples phones are built into themselves
Your deep cuts of videogame references is one of the reasons I love watching your videos. RDR2 had some wonderfully anachronistic characters
It is so more fun and so more beautiful when it is useless. Ben your are an artist ! Incredible work, I love it !
44:50 The point at which Ben's mind goes completely off the rails 🤣
This was a joy to watch. It brings back memories of hacking together games for my TRS80 model 1 in the early '80s and assembly programming in general.
"Apparently people like to hear me sing"
INCORRECT
I must say, the Math Lord lyrics were impressive.
I actually like to hear him sing his nerdy songs, it's hilarious!
I wanna take this and cram it into an early 90's VHS-C camcorder viewfinder.
Having now found the same compiler version as Ben used, I've found I can save 22 bytes of program space by declaring the "gameOver" and "drawPF" functions as static, allowing the compiler to "inline" those functions. This should also reduce stack usage, allowing more valuable RAM to be used. "drawPF" uses four stack bytes, which are saved otherwise.
cackling at the idea of Tony Soprano discovering the power of mathematics. these videos were incredibly fascinating, from someone with no experience doing this kind of extremely low-resources / low-level programming. there's so much creativity involved in achieving things with those restraints!
Why would someone watch the video without understanding it? It seems pretty obvious. Because it's interesting to learn a little bit about game dev, or programing on a very limited platform. While I understand some of this I find your whole thought process and the dev process fascinating
my reason is his singing, his stories and his general way of existing
@@chrisakaschulbus4903 The singing is fine, but I really like his stories and tangents. Like that Star Trek tangent
One of these days I'm gonna work in a Terminator Dark Fate tangent, where I discuss how it completely missed the point of why women liked the Terminator movies to begin with.
@@BenHeckHacks I'd like to hear that tangent. I heard Dark Fate was good (perhaps the best one since 2), I didn't watch it because I didn't like how they killed off John Conner
@@flightlesschicken7769 It was OK but completely missed the point of why women liked the original Terminator.
Yes I’m one of them that don’t know what you doing because I’m a beginner in code but I love to watch the whole video specially the math lord song 😂
I think the most underrated thing is that you’re coding an NTSC video signal in programming vs using hardware actually intended for it.
Honestly in some ways it's easier doing it like this.
Is it easier cos its basic drawings pixel by pixel and not a full image?
My ADHD feels very welcomed by your long-form videos.
As someone who does pretty much all my projects in avr c, I learned alot of tricks from watching this series, now you just need to make a cool themed case for it
It was like watching Ustad Zakir Husain of microcontrollers do his thing. loved your confidence, energy and the atmosphere you make. Like said UStad Zakir Husain.
Ben, I love the way you embrace your nerdiness. You seriously just badly sang the theme to Math Lords and you don't even care. You are truly an inspiration to all nerds.
Ok, that was properly insane.
I approve!
This is a fun little project to be sure, but the aspect that has prompted me to comment is your adorable cat
Maybe minor feedback, but it would have been nice to use #define aliases for the registers you were using untraditionally. I appreciate seeing you make the most of the AVR but after a certain point it would have helped to have more readable names.
Also a small related nitpick: defines or const static variables for various constants/magic-numbers. Iirc you can probably even use a special compiler built-in keyword to create aliases for registers or specific address locations so you can still have a proper variable name but give it a fixed location. May make debugging easier.
There's also a cost to offset positioning elements in a C array that one could hard-code in pure assembly.
man this got so cool, imagine how much better it could get if ben actually spent like 1 month only optimizing the code to implement more things, that would be awesome, but now it's up for the community to elaborate
"And here you can see Ben Hack descend into the madness of re-using hardware registers for variable storage, and spaghetti code..."
I dont know why i have never found this content. omg. this is the best. i have a co worker who builds circuits like crazy circuits just like this. i always want to sit and watch and listen to him as he curses and constructs a monstrosity.
you and him probably know each other. but anyway great content and i can't wait to watch and listen :D
It's amazing how much value you squeezed out of that cheap part.
The super glitchy signal on the big TV adds a certain charm to it.
Executing code in an interrupt is janky, but using the sleep mode control register as RAM isnt? 😂
I think it'd save some space/CPU time if the state machine was restructured so you just had one densely cased switch, and checked the scanline counter to change to the next state. The compiler can turn the switch into a compact jump table if the cases are dense but when it's spanning 131 cases it either has to use a jump table with a lot of empty space or in this case it's probably doing something else like a balanced decision tree.
Your code/state machine can take advantage of the fact that the scanline counter always increases throughout the frame, so you only have to check for changes from one state to the direct next one.
Could work, but switch case is more efficient than if then. The line section status doesn't change very often.
@@BenHeckHacks What I have in mind is a dense switch on a state value (0 to n, no gaps) where each case includes one if (no else) to change the state based on the scanline counter. I don't think the sort of switch you're doing now would be more efficient in either space or speed.
@@BenHeckHacks I don't know if you were running with any optimizations, but gcc-avr will convert if/then trees/stacks into jump tables or decision trees on as low as -O2. You can see it in the simulator after compilation.
"SHUT UP ABOUT PROGRAMMING AND TALK STAR TREK 5!" Love this type of video, Ben. Keep 'em coming. Making gold from dirt here with the Tiny.
48:10 at about this point, I was reading comments and I couldn't tell if Ben had genuinely lost his mind or not :x
...just a stunning insight into how to do something practically impossible for 99.999% of the human race. I doff my cap in your general direction Sir!
Just in case this isn't obvious to anyone... Don't feel bad if you can't program as easy as this. He's not actually writing the code here, he's copying it from where he already wrote it and probably spent a whole lot of time and pasting it in as he's explaining it. Don't feel bad you can't write code this fast or this quickly or easily. There's a lot more work put in behind this than is obvious in the video.
-- someone who's literally done this kind of thing for 20 years and I still can't do it as fast as what he's showing on the screen here. Lol
Yup that's what I did! Notepad++ on the other monitor. Make sure the basic concepts work then explain them - though the issues with sound and low memory were pretty much "real time"
Shhh!
Speaking as somebody that is afflicted with a bug in their tesselator that they don't comprehend? I feel all pain you hint at, Sir!
I came for the crazy bit banging and control register abuse, but I stayed for math lords.
- Why do that?
- Because we can !
Good job Ben !
You are becoming a legendary "grey beard" with those arcane asm skills!
It's amazing. I have watched the two parts for the second time. I love those tricks. Also, I'm wondering if writing everything in assembly directly would reduce the space constraints a bit 😂
That math lord concept was so dark, even the cat got quiet.
Hey there neighbor. I stumbled upon your channel a few weeks ago and love your work. I live in Eau Claire area. After this pandemic is over, maybe we could meet up some time. I am pretty big into printers and electronics myself, although not quite as knowledgeable as you in actually building boards and such. Happy holidays.
I have to say, that's fucking AMAZING!!! I would buy your contraption for 100 bucks just because is so cool seeing you making the code and all that hard work. Would be nice to put it to auction. Or to exchange with other makers, other cool stuff!
Great project, I love the aesthetics on your massive tv
I had no clue of what Ben was doing, but I watched both videos anyway. :)
You could use input parameters, something like
__asm__ __volatile__("out 0x01, %0"::"r"(theData));
So that you don't have to chase down the specific register that was used to pass theData into drawSprite(), especially if it happens to change all of a sudden. The compiler will take care of it all for you. 😄
Edited some more: OUT can only read from registers, so removed the m constraint
Nice!
Sorry, should be % instead of $. If the compiler is gcc, check out the details for "extended assembly". Takes a little getting used to, but nice and powerful for mixing raw assembly inline with C. :-)
edit: and apparently it is better to use "mr" in the template so that the compiler can choose the best way to pass the argument - in "m"emory or in a "r"egister.
@@TomStorey96 In this specific case it has to be the "r" constraint since the out instruction can not read RAM. The "mr" thing is probably not useful on AVR or other RISCy instruction sets.
@@mcg6762 Cool. I dont really have any experience with ATMEL micros. My head is a bit buried in MIPS and m68k in recent times, so Im kind of thinking everything can be just as flexible in terms of sources and destinations. :)
I wonder if the compiler will just ignore the m constraint, if it knows that the instruction cant use memory as a source/dest.
@@TomStorey96 No, the compiler doesn't actually parse the inline assembler. It just substitutes the necessary parameters and let's the assembler deal with it. So you would get an error message from the assembler if it tried to pass in a memory reference.
Cool thanks for the fun video series. Makes one realize just how inefficient "modern" games are.
You can also use {} around the code for the case in your switch statement in place of ; when doing assignment in the case
Ok when you are struggling this hard to make space invaders work, something the atari 2600 had virtually no problems with, I think it would be fair to maybe double the horsepower and make the almost tinniest console imaginable. We will all still be impressed.
I loved every second of this and the first one!
The fact that you need a semi colon when assigning a variable in a switch just blew my mind. I never knew!
Only if it's the first thing after the case: not sure why.
@@BenHeckHacks Sent me down a little rabbit hole of C semantics. Looks like it's a bit of history that means all case labels must be followed by a statement not a declaration and the semicolon just acts as an empty statement. Another way to do this is to wrap it in curly brackets as this acts as a compound statement which is the way I'm used to. Either way it's an interesting tidbit. Cheers and thanks for the great vid
@@alessi4249 Yeah, the curly braces trick works in pure C as well. The semicolon trick only works using a C++ compiler. Or did they fix that in C11 (variable declarations mixed with code)?
Some people are registered abusers, Ben is a register abuser
Ben is back. Truly inspiring content, the whole cat and junk mail thing had me worried.
The full changing of registers is a big deal in big CPUs.
The main thing "Hyper-Threading" does is provide additional register infrastructure to minimise the impact of this.
That's why it provides this much real world performance with so little price increase. IBM does 4 to 8 threads per real core. Because this is a very big deal on most systems. One assembler instructor threw around a figure of about 1000 cycles delay(which in my opinion sounds about reasonable considering the necessary steps and memory delays in general, its probably less in modern architectures as the memory controller was moved into the CPU in AMD and Intel CPUs about 15[?] or so years ago) to access memory.
But i assume modenr CPUs use integrated memory, the cache for example to decrease that number somewhat.
@Christopher Grant Wouldn't surprise me. I mean at those speeds size on this scale already matters.
I also assumed that RAM access in the uC would be faster simply due to it being this much closer. I mean the whole thing is significantly smaller then a desktop CPU module.
Which are partially this big to distribute heat and not kill themselves with their own heat output.
Oh, i just did the rough numbers, you are probably right. at 8mhz the signal can travel over _20 meters_ between cycles. So i think your theory of memory access in one instruction is not that outlandish (in uC, at the very least the ones clocked in lower mhz region. Like this one.).
I did not even consider that. Good thing i ony talked about much faster units. Because over 1ghz the travel distance already shrinks below 20 centimeters. The speed difference might really matter a lot here.
So we can see thermal limits again. 3Ghz shrink it down below 7 centimeters. So it's already getting tricky even with a direct connection and perfect sync.
Which again we don have worry about on a uC the RAM might very well be the equivalent of the L1 cache as you said.
tl;dr: As i understand them the physics support your estimate. Well, as long as the required circuitry is shorter then 20m to which i have no clue. But that sounds like a lot considering how small transistors are.
Maybe someone who desings processors stumbles over this. Or at least soemeone with more transistor logic experience.
@Christopher Grant Just realised: The implication of my text in the brackets:
The memory might be the(/one) limiting factor for the clock speed of the attiny. That would be another point for your theory.
=> Higher speeds might desync due to travel time and therefore break the reliability. Because of the relatively simple design of the chip.
Really interested on whether that might be the case.
So is hyper threading good im sure i see a cheap laptop advwrtised the other day and it didnt have it i was kinda shocked but i dont know what it does so thought it might have been outdated or something but if its still useful then i guess it was just really budget?
Per the "I don't understand what he's saying but I watches the whole thing" comments. To me its like watching a craftsman build something and listening to an interesting person. It sure beats listening to silence as I work all by myself.
I think title would benefit of at least a subtitle, this is not only making a console, this is VideoGame Making 101 and many people would miss the video as it stands now when searching for videogame coding tutorials.
Amazing video by the way, thanks a lot Ben!
I love how incredibly hacky this is. Using unused peripheral control registers as extra memory ... Genius.
You could use one of those 4 pole 1/8inch connectors for audio and video like many cheep cameras do
finally! an hour + long video from Ben Heck
I watched both videos completely. I understood some of the words....
I finally discovered Steve1989mreinfo and boy oh boy THAT was a 3 day rabbit hole. Anyhow I can now related to yet another of Ben's references.
"you're tearing me apart Lisa!" And I absolutely remember carmen san diego. Haha. I think I would have opted for #define rather than refactor in this scenario... Just to retain useful variable names. ;)
36:59 this is the part where the bits break down, everybody's tense no one makes a sound
also this is brutal, like going backwards from high level (my exp) down to C ha
still is a fun challenge to work with limited resources
45:20 ha
46:30 the song reminds me of "making your way in the world today" from Adventure Time though not original source
1:04:05 lol IT'S ALIVE!!!
I give props making long videos like this especially the code part as that's a lot of stuff to sift through and arrange to be entertaining to viewers
1:11:30 haha
1:16:47 it's one of those things you put in your garage to keep the critters away
You know your stuff regarding this beyond me and I have been programming professionally since 1986 and C++ since 1993 but I do have a suggestion for readability. make that __asm__ __volatile__ ("nop"); a C macro named something like NOP. You are using it enough to be worthwhile at this point. When I mentor programmers I try to encourage them to avoid too much cut and paste such to avoid now many mistakes your replicate or how much code you have to change to change a behavior.
For those special registers where you repurposed some of the bits are they actually only storing the bits that have meaning or do they have a full set of bits with some of them being unused? If they are just unused you could really store a lot of bits across all of them.
Some of the registers that only have a few bits (like prescalers) have a "virtual" machine word, so you'll see 8 bits. However, some of the bits are tied high or low and if you were to try and set them, which Ben does here, because you can only set full words as the smallest unit, it'll succeed, but those unset-able bits will just remain their constant value.
Just realized I'm watching nearly everything you're uploading and I didn't bother subscribing! Fixed now 🙂.
I'm in love with the math lord idea! And that song is *chef's kiss* the cherry on top!
Ever thought of attempting a super nintendo oscilloscope cartridge? I just had the thought the other day, it would suck, but be pretty sweet too
That's actually a genuinely cool idea, even if it would suck.
It would be interesting to see what a demoscene coder could do with one of these. I have already seen some pretty impressive demos on ATMEGA chips and 128 byte intros are popular these days. This might be tricky but you never know...
It's been done on an ATTiny15, which has slightly less RAM: www.linusakesson.net/scene/bitbanger/index.php (Albeit on VGA instead of composite.)
Nothing but registers? Yikes.
In theory you could do VGA on the ATTINY 10 but you'd only have one pin left for input and no sound.
Amazing build, and super fun project. Thanks for a great video... as per usual...
I wish I could program like you some day. Thank you for keep inspiring us with your amazing projects.
My comments on the video:
That is genius .... using all those reserved registers :D
Ben, Hollyhood would like a word with you.
The poor cat... also headphone users... specially younger ones, that thing was throwing some high pitch audio...
Yep, those registers are special for a reason... That is a lot of code optimizations :)
That is an hot glue mess :p ... no pretty ergonomic container to hold it 'safely'?
Good video, educative and entertaining ... have like :D
Yo! This is some valuable knowledge. Thanks for walking through it.
I'd love to see this put into an old RC car controller. The one with the side mounted wheel and a trigger.
Ha! I'm one of those that watched the whole video and have not too much of a clue what you're talking about, but find it fascinating none the less.
2:10 that looks like you could get away without the ifs even, saving you some code size, since you do the same in both branches: set portb&ddrb, wait, set them again. so you could make the values you set them to the result of a multiplication with `SMCR&0x02!=0`.
Interrupts are not nearly as inefficient as you imply. The routine only saves what you want, it WILL save a return address to jump to after the routine. It is likely way faster than an if() statement. Saving more of the registers is up to the user. The compiler is amazingly efficient at minimizing unnecessary actions but will obviously save more than you would if you wrote you ISR in assembly.
I didn't document it but in the simulated disassembly the original code's interrupt was push/popping something like 8 stack registers.
Yeah, interrupts obviously have to switch context and push/pop all registers which the interrupts would otherwise overwrite. While the interrupt processing in the CPU is nearly free, the context switching may not necessarily be so.
@@BenHeckHacks This is a dumb hack, but I did it before in a project. If you keep all of your variables completely global, pass nothing around, the only thing that would get push'd is the PC on an ISR.
The concern about if statements in optimization has to do with invalidating the instruction cache and purging the pipeline. This thing probably has neither, so there's probably no reason to avoid if statements.
Don't use the ATtiny25/45/85 in the future, use the ATtiny13! It has the same 120 instructions as the ATtiny25/45/85, and twice the amount of RAM compared to ATtiny10.
I think I have some of those!
How about doing a 'dual-processor' project? using two in sync / communicating - or make 'discreet chips' using several of them? they're so cheap! you could use single ones as gates if you wanted to :P a little socketed mainboard - a 'video chip' and a 'cpu' it would be so beautiful! (could you use one as 'RAM' using a bank switching technique? 4 pins of two micro-controllers (splitting+doubling memory), using the other pins to switch the bank? well I guess 8 of them for address pins? perhaps use the flash memory as ROM for graphics/code too? - you could use one pin to sync and do a kind of time-slot/auto-increment or delay line access to save on address pins)
Thinking about doing self enumerating serialized solenoid controllers for pinball machines.
@@BenHeckHacks - Mr Ben, Sir - whatever you choose to share? will be a pleasure to learn (or a pleasure to fool myself into thinking I'm starting to understand) :) You're right up there with John Carmack in my eyes. The ATTINY game console makes me feel like I'm seeing Doom again for the first time - easily better than 2077
Clicked like before I even started to watch because I knew it was going to be awesome.
No wukkas means no worries. I believe it stems from us Aussies saying "no wucking furries" instead of "no f'ing worries"
For a moment i thought ben eater has got a 2nd channel thats just for funn..
Awesome videos btw i love this type of content. I hope you live longer than me
I was tickled at your suggestion to use the AVR Studio 'simulator' for debugging, (since I thought of this quite some time ago.) Another approach for debugging is to mostly develop the app on a 328p, which is easier to work with, and does have hardware debugging available. Atmel did a great job at keeping as much compatibility as possible. So, provided you pay close attention to the subtle differences between the instruction sets, peripherals, general purpose registers etc., you can then port your code over with minimal changes once you're happy with it.
Very cool project...On a side note a drinking game too - drink everytime Ben says bug.
Why not #define variables to be registers instead of refactoring into a nameless blob? Watching this is kind of like watching “Junkyard Wars” for programming ;)
Okay so this is something I've struggled with for years. If you define that it makes your code more legible but it becomes easy to forget what that actually is. And we know now that if we set certain bits there, we will end up causing unwanted behavior. So the question is do we call it what it actually is or do we rename it in an effort to make it easier to read the code?
in this case the whole application is so small there is a good argument to just put a comment next to the areas you need one and remind yourself what you're working with...
@@TravisFabel Quite a symble fix for that. Just mark the number of bits. Two more characters is not going to make it unreadable, and that's really all you need to remember in a case like this.
In other words "#define _"