In a previous video we built this circuit that generates a valid VGA signal, and when we plugged a monitor in we saw that the monitor recognized that it was in 800 by 600 mode. And that's because we're sending at the right horizontal and vertical sync pulses for that mode. We're sending a 3.2 ยตs horizontal sync pulse 38-whatever thousand times per second and we're sending a 0.1056 ms sync pulse 60 times per second. And the way this all works is we have a binary counter here that's counting from 0 to 264 you know over and over again, and it's counting at exactly 10 million times per second. So we can look for these particular values to get the pulse width and the pulse frequency to match exactly; and then same thing in the vertical direction, you know we've got another counter here that counts each line on the screen from 0 to 628 and of course, you know we measured all the timing and everything in the last video to make sure it matched the specs and it did and so we saw the monitor recognize it as a valid signal. So if you want to know more about what's going on here, I definitely recommend you check out the first video, but now you know we probably want to actually display something on the screen. And to do that, the VGA interface has a few more signals for red, green and blue. and Because we're using the sync signals here to stay synchronized with the monitor we can use our horizontal and vertical counters to keep track of what part of the screen the monitor is currently "painting" at the moment. And so the 0 to 200 tells us where it is from left to right, and then the 0 to 600 Will tell us where it is from top to bottom and so as the monitor paints the screen You know, left to right, top to bottom: the same way you'd read a page in a book. These red green and blue signals is how we tell the monitor what colour pixel we want at that particular location. In other words: because we're synchronized, we can use our counters to know exactly what pixel the monitor is.painting at the moment So for example if I take just one bit from one of these counters (so this is the the vertical counter) if I take those, you know 1 2 4 8 16s place Let's say this 16 s placed bit right here you know as we count down the screen from 0 to 600 down the screen this bit is going to flip on or off every 16 scan lines. So if I take that bit and hook it up to the the green signal and I'm hooked it up through a resistor (for reasons that I'll get into here in a minute) There we go You know You can see every 16 lines the green flips on and off and that's because as our article counter counts from 0 to 600 every 16 counts this 16 places bit flips on and off because that's how binary counters work Now if I hooked the red up here to the eights place in the same way There we go and now you now you can see the green and the red as well as Yellow when the two mix and then of course, you know, I can also hook up blue So, let me just do that for the sake of completeness And now you can see the three primary colors mixed to give us actually 16 distinct colors but you know as much fun as colorful stripes are what I really want to do is display a more complex picture and for that to work We need the pixel data stored in some memory somewhere Normally in a computer the data for whatever's on the screen is stored in a part of RAM that way the software running on the computer could just write new data to the same Ram location and the image on the screen will Change right away, but I'm not gonna hook this to a computer at least not yet So I'm just gonna store the image on an EEPROM and this is a 28 C 256 EEPROM so it'll hold 32 k of data which should be enough for an image of some sort and the way this will work is we already have our horizontal and vertical counters giving us an x and a y position and if we feed all of those signals into the addresses of the EEPROM Then the EEPROM will give us a byte stored at that address and that byte could be the color of the pixel for that particular XY location So that's how we'll store the image in the EEPROM But there's one weird issue which is because we went with the 10 megahertz pixel clock instead of 40 megahertz we only have 200 pixels by 600 instead of 800 by 600 and 200 by 600 is kind of a weird resolution No The pixels would be kind of stretched out because really what we're doing is we're repeating each pixel four times as we go across So it'd be nice to maybe slow our vertical counter down. So we repeat each line four times as well And it's actually pretty easy to do if we have our counter counting in binary like this You know here we're just counting 0 1 2 3 all the way up to 16, you know, just counting in binary Well, if we lop off that last bit now it goes 0 0 1 1 2 2 and so forth up to 8 and if we lop off another bit Then it repeat each number four times and only counts up to 4 instead of counting to 16 So if we just ignore the bottom 2 bits of our Y counter that all divided the counter by 4 We actually still have another problem which is the EEPROM. I have the 22:56 it actually only has 15 address lines so we couldn't fit all these address lines here Even if we wanted to so we've actually got to get it rid of at least three address lines in order For it to actually fit in this EEPROM So given what we've got with this EEPROM and to keep the right proportions end up having to drop three bits from our Y counter and one bit from the X counter and that leaves us with a Final image resolution of 100 pixels by 75 pixels which you know, I don't know it's maybe not the most impressive resolution But I mean, what do you want for me? I'm trying to build a video card on bread boards So 100 by 75 is is what we get. So let's hook the EEPROM up like this I'll start by adding another breadboard and connecting power and ground to the EEPROM Then I'll tie the right enable pin high since it will only be reading and it's active low and Output enable and chip enable are both active low as well So I'll tie them both low, so everything's enabled for output Then the first seven address lines go down to our horizontal counter and again We're skipping the first bit and the next seven address lines go to the vertical counter and this time skipping the first three bits So that's 14 address lines 7 for X 7 for Y. There is a 15th address line, which we're not going to use So I'll just tie that to ground So now we've got all of our address lines connected like this So for each of our 100 by 75 pixels, we should be getting a byte of data out here That'll presumably tell us what color that pixel should be But how are we going to turn this 8-bit data into the signal that the VGA monitor expects? well the VGA interface has three pins red green and blue and each expects a voltage between zero and point 7 volts and Depending on what that voltage is Whether it's closer to 0 volts are closer to 0.7 volts Determines how much of each color is mixed together to determine the color of the pixel? But we've got eight bits here of data that are either 0 volts or 5 volts. How do we get something that's point 7 volts well is the case where we have one voltage and we need a lower voltage and so we can use a voltage divider and A voltage divider is just two resistors like this. So relative to ground down here We've got 0 volts and up at the top. We've got 5 volts and in the middle here It's going to be somewhere between 0 and 5 volts and how far it is from 0 volts to 5 volts Depends on how big r2 is compared to the total resistance So if r2 is half of the total resistance that is r1 and r2 or the same value then in the middle here We're gonna have half the voltage. So two and a half volts if r2 were 10% of the total resistance Then we'd have 10 percent of the total voltage or half a volt So this expression just describes that? You look at the proportion of r2 to the total resistance And then that tells you how much of the total voltage you get Now the VGA spec says that the red green and blue signals it says, you know zero to 0.7 volts It also says it's got this 75 ohm input impedance and You can roughly think of that as meaning that inside the monitor Those red green and blue inputs are kind of connected to ground like this through a 75 Ohm load of some kind so if we've got five volts and we want to get it down to 0.7 volts as it's going into the monitor here. We can just add a resistor like this and that creates a voltage divider, right? So we've got five volts at the top We've got ground here at the bottom and we want this to be 0.7 volts here in the middle Now if the monitors input impedance is 75, ohms we can't change that but we can put whatever resistor we want here between our 5 volts and the input to the monitor and You know to figure out what resistor that needs to be in order to get 0.7 volts well, we could just look at this expression here and say you know When would this expression equal 0.7 volts given that r2 is is going to be 75, ohms well, we can put 75 ohms in here for r2 and set it equal to 0.7 volts and we just need to solve for R So 5 times 75 is is 375 and then R + 75 times 0.7 is gonna be 0.7 R + 52 and 1/2 Subtract that 52 and a half from both sides we get 0.7 R Equals 3 22 and a half we can divide that by 0.7 and we get R equal to four hundred and sixty point seven. Ohms So if we take five volts and we put it through a four hundred and sixty point seven Ohm resistor will have point seven volts here going into our monitor, you know given that there's a 75. Ohm load inside that monitor But ideally we don't just want point seven volts or zero volts going into the monitor here, you know ideally we'd be able to have a range of voltages between zero and point seven volts so we can get different brightnesses of red green and Blue, yeah, so that we're able to have different shades of colors so for example If we wanted four different voltages from zero at 0.7 Evenly-spaced so we could get four different shades of red green and blue We'd have to solve this equation essentially for each of those different voltages to find out what resistor we need to get that particular voltage So that's what I've done here. That's what these different resistances are and This is for you know, one-third brightness two thirds brightness, and then of course full brightness at 0.7 volts Which is what we just we just figured out is four hundred sixty point seven And we could use these different resistances to get these different voltages so that we end up with these different Brightness levels for each of the colors and of course, you know, they don't make fifteen hundred fifty five point four Ohm resistors they make you know fifteen hundred. Ohm resistors and they don't make 720 two point nine. Ohm resistors, but you know, I've got 680 ohms which is maybe close enough So if we use those resistors to build something like this that's got two inputs over here that can be you know Either 0 or 5 volts and an output over here and check this out You know if both inputs are zero like this then the output table. These are going to be 0 volts, right? But if one input here is 5 volts So the one hooked to the 1.5 cave' resistor is 5 volts and this is a zero then essentially we're kind of in this scenario Here where we have, you know, approximately 1,500 1,500 ohm resistor and we're gonna get about 0.23 volts over here But if we flip that around and we we don't have anything here and we have five volts down here Then we're going through the 680 Ohm resistor and we're gonna have you know about 700 or well we're gonna have about 0.47 volts But then if we have ones on both of these inputs, so both of these inputs are 5 volts Well, then these resistors are going to combine in parallel and to combine resistors in parallel You use this expression here, which is sort of the sum of the reciprocal of the sum of the reciprocals So to combine 1,500, ohms and 680. Ohms in parallel like this It's going to appear as a single resistance from 5 volts to this point over here of 468 Which is pretty close to here and so we're going to get 0.7 volts, so this takes two inputs that can either be 0 or 5 volts and gives us the 4 different possibilities that give us these 4 different voltage levels from 0 up to 0.7 volts and So with just a couple resistors we can take 2 bits of binary data and convert that into one of 4 different voltage levels from 0 to 0.7 volts and If we do that for red green and blue we can use six bits of data coming out of our EEPROM to get four different shades of red green and blue which combine into 64 different colors and so this is the mapping for those different colors If you're familiar with these hex codes for colors But you can see the last two bits here controls how much blue there is now the middle two bits controls how much green there is and then the first two bits controls how much red there is and Then this here is what those actual colors look like And so those are the 64 colors that were able to generate with a circuit like this So let's actually hook these resistors up to the outputs of our EEPROM and to here the 1.5 K resistors And of course, there's three of them one for red green and blue and here are the 680 Ohm resistors and so hook the first two data bits up for blue And the first one is going to go to a 1.5 K resistor and then the next one will go to a 680 Ohm resistor, then the next two bits will be for green. So go to a 1.5 K resistor and then to a 680 Ohm resistor and then finally the last two bits are gonna be for red The 1.5 k resistor and the 680 Ohm resistor and then we just need to tie this side of the the resistors for each color together So they'll do blue Green and red. And so now over on this side we should be getting that voltage between zero and 0.7 volts So now we can try hooking this up to the monitor again So here's our 15 pin vga connector and i've got the the same sync signals before in the ground, of course But now i've got the red green and blue hooked up to pins 1 2 & 3 so we can hook up ground and hook up our sync signals as before so the horizontal sync and The vertical sync and then the colors we could just hook up over on this side of the resistors So there's blue there's green and there's red now if we power up our circuit and plug in the monitor So we basically see the same thing as before so the monitor comes alive so it's detecting the sync signal but it's still a blank screen and Of course, that could be because what's in the EEPROM is just blank But I happen to know that this is an erased EEPROM and when you were to race an EEPROM it it just writes all one's So we should actually see a white screen and we're not but I think the problem is that if every address in the EEPROM is Set to all ones then We're gonna be outputting a white for every position and that includes even in the blanking time here And we really shouldn't be putting out any pixels in this blanking time because well it should be blank And in fact, this is one of those things that might actually damage a CRT monitor and hopefully we won't damage my monitor But the way we can fix that Is use these signals that were detecting here for the blanking intervals, right? Because we're actually detecting when we're in this horizontal blanking interval Right here with this flip-flop and we're detecting when we're in the the vertical blanking interval down here So really what we need to do is we need to figure out are we in the blanking interval and those together? and we could actually use the result of that for the output enable signal for our EEPROM so we can essentially turn the output of The EEPROM off if we're in that blanking interval so what I'm going to do is add a NAND gate here and I'm using a NAND gate so that the output is inverted because our chip enable or not our tripping well actually both But the output enable which is the one we're going to use the output enable is active low So that way if if two inputs here are high then the output will be low and then the two inputs that we'll use is well look at if we're in the display period for horizontal and the display period for vertical so, let me hook up power and ground for our NAND gate and so the first input here is going to say are we in the horizontal display period So in other words, this is saying are we in this time here? and then the next input is going to say are we in the vertical display period So that's going to say are we in this time here vertically? If the answer is yes if we're in both of those then what we want to do is we want to take the Output of that and use that instead of just always tying our output enable low. So here we'll say output enable Only when both of these conditions are true So now let's reconnect our monitor and see what we get And that's still not working But it's possible that I've got these mixed up and so I'm actually outputting only during the blanking interval So, let me just switch this over to the inverted outputs on both of these And there we go now it actually looks like we're getting looks like a white screen So if we want to display something a little more interesting then we've got to come up with a little more interesting image Let's find an image. That's nice and colorful. This looks good public domain. That's perfect So I'll download I guess, you know, we're gonna reduce the resolution here quite a bit. So it doesn't matter too much So then if I load that image up in Photoshop, we can resize it So we want our image to be 100 by 75 So 100 by well make it 114 by 75 and then we can crop it to 100 by 75 Okay, so there's our image with the resolution that we have on our screen. It's now we need to change the color modes Who are using indexed color and we want a custom palette? and So these are the 64 colors that we can produce with our resistor voltage dividers and they're also in the in the right order so for example an index of 8 is this You know green color and if we have an 8 in our EEPROM then that's going to produce that same green color So it's pretty important that we have the right colors in here and we have them in the right order So if we apply that This is the image we get so those are the 64 colors that we support and it's a 100 by 75 So that is that is the best our wonderful breadboard video card can do so, let's go ahead and save this We'll call it image dot PNG And if we actually look at what's in that file if we see, you know, there's more than just a pixel data, right? There's a header here PNG. There's you know appears to be some XML stuff in here So there's a whole bunch of stuff in here that we definitely don't want to write to our EEPROM so what we've got to do is we've got to convert this PNG data into the pixel bytes that we want to put into our Indoor EEPROM and the best way I can think to do that is just with a little Python Script, we can use a Python image library to make a little bit easier. So pale is this Python image library And we can open up an image file and then we can load the image and when we load the image what we get is we just get a two-dimensional array of the pixels and because we're using index colors with 8 bits and You know, we've got our colors in the right order here with the right index values the pixels that we end up loading are going to be the values that we want to actually output from our EEPROM to get those particular colors So really what we want to do is we just want to write all those pixels to a file without any of the PNG header Information without any compression without anything like that We just want to write the raw pixels to a file and then we can write that file the EPROM That's what I'm gonna do is just go through all the pixels. And since our image is 100 by 75 I'll just go through all 75 lines and then go through all hundred pixels per line and then I just want to write the value of that pixel to a file so I'll create an output file just call it image bin and we'll just write to this as a binary file and What we'll write to it is we'll write a character that has the value of the pixel so the pixel is going to be coming from our pixels array and it's just gonna be at the XY value that we're at So if we run this that'll create that image bin file And if we look at the image that bin file what we should see in here is we should see all of the pixels so, I don't know if this looks like a bird to you, but One sign that we're on the right track here is that all of these values in here are less than 64? Or while they're in hex, but they're less than 40 hex which is which is 64 So at least everything seems to be you know in the right range But this isn't quite how we want to organize the data in the EEPROM because remember we're using 7 bits for the exposition and 7 bits for the Y position and so because we have a hundred pixels per row We have this X counter that goes up to 99, but once it gets to 99 It doesn't just go to 100 it goes back to zero and then we increment our Y position and so the actual address in the EEPROM will jump from 99 to 128 because these all go to zero and then this bit will be a 1 and that's the hundred twenty eights place so in the EEPROM really even though we only have a hundred pixels per line on the screen in the EEPROM we want to take Up 128 pixels per line, but that's easy enough. We can just for each line instead of a hundred we can just do 128 and Then because we're writing that 128 times we'll end up writing 128 things to the file. So let's run the conversion again and we get an error so image index out of range and that's because you know our image only has only has 100 pixels across so You know when we're trying to get pixel 128 or even pixel 101 or 100 that pixel is not actually in our image So what we can do is we can just try to catch that condition So we get an index error when we try to get that pixel. That's not actually part of our picture What we can do is we can still write to the file But the character will write to the file would just be I mean, it could be I guess we'll just write a zero So let's run that and now if we look at our image We still see the data but what we see is we see, okay This must be the first hundred pixels, and then we have a bunch of zeros that Pat it out to 128 and so you can see like this block here That's the first line and then this next block here with all the zeros padding it out to the end That's the next line And so this should be the format of data that we want to put into the EEPROM Now I suppose I could use the EEPROM programmer that I built in a previous video But in this case because I've got all the data in a file and everything already It's probably easiest just to use a commercially prom programmer. It's also a lot faster. Let's pop our EEPROM out put it in the programmer Then I'll use Mini Pro which is just an open source EEPROM programmer tool that works with this programmer and Will tell it we're using the 1828 C 256 which is the EEPROM We've got and we want to write the image bin file to it and it says incorrect file size 9600 so that's because I guess it's expecting a file. That's the exact size of the EEPROM and we have a 32 K EEPROM So we need to give it a 32k file And the reason our file is is smaller is because our Y is only counting from 0 to 74 even though you know This is 7 bits so it could go up to 128 And actually there's an a 14 right that's that's this extra address line that we just tied to zero So there's actually another 8 bits here So our Y value really could go from 0 to 255 But we're only writing the first 70 75 values of that and that's just so that our you know image aspect ratio makes sense We can do the same thing here. We can just change this to 256 and Then anything that's out of range will just write a zero to the file So let's reconvert and then if we take a look at our image file you can see the image but then if we go all the way down to the you know if we get all the way down at the bottom it says we have got a row of zeros and then I think this star just means everything is zeros between there and Eid 0 0 0 which is 32,000 So let's try to write it again. And there we go. It's writing it And so now let's put our EEPROM back in and see if we get the picture of that bird Hey and there it goes and So this is our completed video card, but you know, it's producing an image from the ROM. So I'm going to call that a success Well, you might notice these thin black vertical lines and I think that's because the ROM I'm using is relatively slow. Yes I'm using a 10 megahertz clock which means it's counting 10 million times per second or you know Once every hundred nanoseconds remember the monitor still assumes I'm using a 40 megahertz clock so really the monitor is expecting a pixel every 25 nanoseconds and So here's the problem if we look at the data sheet for the EEPROM that we're using There's a delay from when the address goes valid. The address lines are valid to when the output is valid That's this TAC C or access time and if we look at access time here the addressed output delay It's a maximum up to 150 nanoseconds for the dash 15, which is the one I'm using Well 25 nanoseconds per pixel 150 nanosecond delay could mean up to six pixels could be invalid in this in this period from when the address changes to when the output is actually valid and you know It makes sense that sometimes invalid might just mean that the output is zero And so I think that's why we sometimes see a few black pixels like this Now in a computer like I mentioned before the image would be stored in SDRAM Which is designed to be very fast access for this very reason And of course the you know the software running on the computer could write new data to that RAM and the image would change right? away whereas here the only way to change the image is to reprogram and/or swap the EEPROM which as You can see even if I speed it up when I edit. The video is is pretty slow But you know, that's not really the point is it, you know Really the goal was just to get some kind of image on the screen and I'd say this is a success And if you want to try this yourself, you can head over to my website I've got schematics data sheets and where you can get all the parts and as always, you know I want to thank my patrons for making it possible for me to create videos like this as you can imagine these were rather time-consuming videos to plan out and produce and without the support of these people and all my patrons who are just viewers like you Making this kind of video just wouldn't be possible. So, thank you
World's most simple explanation of a DAC snuck in there too. Neat.
Could totally put a nice slow (~1Hz) clock on that last memory address line for a simple two-frame animation. But I suppose the computer is the real goal here!
Fantastic. I thought he was going to be finished after getting the stripe colors on screen. Then he got RGB data and explained all the steps required to get the data in the right format
Looking forward to seeing him hook this up to his 8-bit breadboard computer.
^(4 frames around this timestamp for those that missed it.)
I made a video card/game on an FPGA. It implements VGA and has controls in the form of 4 buttons. I also implemented hit boxes for the top, bottom and sides of the screen. It is about 400 lines of Verilog and was the hardest thing I've ever had to write.
Fun fact.. even though he used a different image in his actual video, in the thumbnail he shows the famous Lenna test image.
https://en.wikipedia.org/wiki/Lenna
Which is actually a cropped (safe-for-work) version of an actual Playboy centerfold from 1972.
This makes me appreciate hardware abstraction layers, and all their weirdness like buffer on the device vs. buffer in ram. (Device dependent bitmap vs. device independent bitmap for windows users)
Damn, that was interesting as hell.
I think this actually motivated me to finally start learning the electrical side of computers.
There is only one thing I do not understand: When he connected the single bits to the color wires, creating that horizontal stripes, he did send them also during hblank and vblank, and they showed without problems. When he tried to display the white screen from the eprom, he had to turn off the output during vblank and hblank. What can be the reason for that?