B1 中級 14 タグ追加 保存
I built this simple computer and wrote a program to print Hello World on the screen.
But the program's a bit of a mess.
Right now it's over.
100 and 50 lines of assembly language.
Just a print Hello world and see bringing character by character here.
Uh, it's very repetitive because I just copied and pasted the code to print each character over and over again.
And then if we go all the way back up to the top, I'm doing the same thing at the top here just to send the instructions to get the display module set up in the first place.
So let's simplify this by moving this code here for sending an instruction into a subroutine that we could just call with a single line of code each time we need to.
I'm gonna copy all of this and paste it way down here at the bottom of the programs is all the way the bottom will paste that same code, and there I'll add a label here called LCD instruction, except I'll spell it right and I'm calling an LCD instruction because this is the code that sends an instruction or whatever instructions in the air register to the LCD module.
So back up at the top.
Ah, here's here.
We're sending some instructions.
Were loading the instruction into the register and then instead of having all this code here, we just say J s R, which has jumped to subroutine LCD instruction.
So this will load the instruction into the register and then jump down to the bottom here to this LCD instruction section here and execute all this code here which actually sends that instruction to the LCD.
Then at the end of this, we can say our T s, which means return from subroutine, which will exit the subroutine and then jump back up wherever we came from.
So if we go back to the top here, that'll jump up here, Thio, where we just finish this jump subroutine instruction.
And the nice thing is that we can reuse that subroutine for each of the instructions we need to configure the LCD.
So instead of doing all this will get rid of that and say, J s r l c, the instruction and the same thing here is another instructions would get rid of all that, and at another jump to subroutine LCD instruction.
It's now it's easier to follow this code because it is here.
Yeah, this is all the code to initialize the LCD module, and you can check out my last video where I go into what each of these instructions do and what each of these bits are for.
But if you want that video and know what these bits mean, then hopefully his code is pretty clear about what it's doing.
And of course, you know, I've also got the comments over here, which should help, assuming they're correct.
And then, of course, we could do the same thing for actually printing the letters.
It's all of this code here prints the letter H from the register so and copy all that down into a subroutine down here at the bottom as well.
And I can call that print character, and it's pretty similar to the subroutine for the LCD instruction.
It's just we've got this register select bit set here, but again at the end, we need the return from subroutine instruction to exit the subroutine and go back up to the main program.
And now we go back up here.
We can replace all of this with just a jump subroutine frank character, and I'll go ahead and do that for each letter in the entire message.
And you can see this is going to significantly reduce the amount of code we need to send each letter, since most of the code we started with was just repeating the same commands to send a letter to the display.
And now that's been factored out into that suburb.
Now this is what we're left with, and it's saying it's a lot shorter and also, I think a lot more clear about what it does.
And the thing with assembly language is there's a direct correlation between the assembly code that you're right and the machine code that runs.
So let's save this.
And if you look, the machine could we started with Yeah, this is all the assembled machine code.
You can see there's a fair amount of it now from the assembler on our modified code.
Now, if we look at the new code that we generated, see, this is a is a lot shorter.
That means is also gonna take up a lot less room in the rum or wherever we're running it from now, they're I guess there is a tradeoff in the sense that jumping to and returning from the subroutines will take a few extra clock cycles to execute.
So technically, this code will runs slightly slower than what we had before.
But you know, there's a huge improvement in size, so yeah, I think that's a fine trade off to make.
I've got the pram in the programmer, and this is just writing the new code to it, and it's done.
So let's put the problem back in the circuit, and it should work the same as before, because we haven't changed the functionality it all.
We've just factored out that repetitive functionality into into the subroutines.
So power this back up and then I'll reset it here.
It should work just the same as before, and we should see it.
Print Hello, World on the screen, although it looks like maybe it's not working and try resetting it again.
And, yeah, it's not working.
So what's going on here?
What we could do is is step through the program and see and try to see what's going on and in order to see exactly what's happening.
I'll connect up a bunch of pins from this hard.
We know omega that I've used in some of the previous videos, and so here all connect a 16 pins from the Arduino to 16 address lines, which will let us use the Arduino to see exactly what value is on the address lines at all times.
And then I'll connect another eight pins up to eight data lines on the bus, so we can also see what data is on the bus at any point in time.
And then I'll hook up one more signal here to the reed right pin to see whether the processor is reading from our writing to the bus.
And then finally, I'll hook up a signal to the clock pin because I've got the Arduino program to read all these other pins just that moment, when the clock signal pulses so we can follow each clock cycle.
And then, of course, we need a common ground connection between the computer and the yard.
We know.
Let's put the Arduino in and then power up the computer, and this is the same artery.
No code from the first video in this Siri's.
If you want to know more about how it works, But if I pop open the serial monitor here, we can see Yeah, there goes.
So it's it's definitely doing something.
And this first column here tells us what address the address lines are on.
So this is the 1st 16 wires that I hooked up there that shows us the address bus.
The next eight bits here of the second column shows us the data bus.
And then we've got the address bus here, Justin Hexi decimal and then over on the right.
This is the data data bus in Hexi Decimal.
And then this column here is either R W to show whether the processor is reading or writing to the data bus.
Let's hold down, reset now and then to stop the clock, and then I can clear the output here.
And then it takes seven clock cycles for the processor to initialize soul.
Step through seven clock cycles.
And incidentally, when I made this circuit to do the single stepping, I think I may have made the pulse with a little bit too short.
So it still does sometimes bounce when you let go of the switch a quick hacky work around is just to put a point.
One micro fared capacity right across the switch, which I did hear and that that seems to help things a little bit.
But in any event, after that initialization it, then reads the reset vector and gets 8000 There's gonna jump to address 8000 which is the start of our program.
If you didn't follow all this, you know that's that's fine.
I cover all this in the first few videos if you want to go back and watch that, but at any rate, at this point where it address 8000 and we're reading a nine, which is the first load a instruction because a nine is the op code for low day.
So if you keep going here we get low day FF and then ate deice story.
And so that's a store A 6002 That's this store a D D.
R B is that on the next clock cycle, we see it writing that FF to address 6002 If we keep going, we get load A e zero and then store, eh?
6003 Such the story, DDR A And now we can see it writing that e zero to address six years or three the next we get Load a 38 and that's our first instruction that we want to send to the LCD.
So, so far, so good.
And now we're reading Ah to zero, and that should be the jump to subroutine.
And, you know, if we look at the data shed that that is indeed correct to zero is is the op code for jumped a subroutine.
Okay, so then next we expected to read the address, too, to jump.
So it's reading five D and then five t again.
But this is this is sort of interesting.
So for some reason, it's now at address 0124 and it's reading, and it's I don't know where it's getting this five d from.
But after this jump to subroutine, I would expect it t read two bites.
I would expect it to read a bite from 800 d and 800 e because that's where the address too to jump to is.
But instead it's going to the 0124 which, which is sort of weird.
So So let's keep going.
So wait a minute Now what's it doing?
So now it's still at that address.
0124 But now it's writing and it's writing 80 And if we go one more clock cycle now we see it's, you know, at address 0123 And it's writing zero e.
So So what's up with this?
You know, we had this jump subroutine instruction and then it seems to have read half of the address to jump to.
But then it's going into the these other addresses, and it's, you know, looks like it's writing something to those addresses.
So So what's up with that?
Well, let's let's actually keep going a couple more steps.
If I go one more step, it actually seems like it's maybe back on track here, right?
Because we had 800 See, it's reading the op code for the jump to subroutine than 800 D.
It's reading five D and then 800 e eventually.
After all this weirdness, it's reading a zero, and that part seems right because I have a jump subroutine And then, if you look at this five d and this 80 that's the address it's jumping to.
And those are the next two bites after the instruction.
So so that sort of makes sense.
Job subroutine 2805 D That sounds right.
And in fact, if we go one more step here we end up at address 805 d, which is, which is to be actually beginning of our subroutine.
So this this is doing the right thing.
But it's doing something else kind of weird here with this address.
0124 and it's writing some data there.
So what's going on with that?
And what it's doing here, particularly these two clock cycles here, is actually really important because we're jumping to a subroutine and the way we expect this to work.
If I go back to the code here, you know, we are at this point here.
So we're doing this jump subroutine to LCD instruction, an LCD instruction.
Apparently the address for that is this.
805 d, and we are eventually reading that right?
So we're reading the jump subroutine instruction.
So that's the jump subroutine here and then the ah, the label or the address to jump to is LCD instruction.
And it's gonna read that, you know, the instruction was at eight zeros there, see?
So that's gonna read 800 D and then eventually 800 e to get that address 80 five d and then it jumps to 805 D and then 805 d is LCD instruction.
So that's all the way down here.
So this LCD instruction, the first instruction there should be store A And so if we look here, is there a five d?
We're reading an A.
D and D is in fact, the op code for a story.
But you know what's going on here?
What's going on here in the middle of what is this writing?
What is this?
01240123 What is all that nonsense?
Well, what's going on is, once we get down to this LCD instruction, we've jumped down here, we're gonna start executing all this stuff, and then eventually we're gonna get to this return from subroutine, and we're gonna need to somehow find our way back up to where we started appear when we called that subroutine so that we can continue executing our program.
And so what addresses that need to jump back?
That's the That's the question, because the return from subroutine doesn't you know, doesn't give an address.
It just says return from wherever you came from.
So the processor actually has to keep track of where we came from, and that's what it's doing with these extra clock cycles here and specifically, what it's doing is it's writing.
It's writing this this 800 e, which is the return address that we're going to have to return to because a deer is there.
He is actually the last address that we that we that we came from.
So So we're really what the process is doing is it's taking a couple extra clock cycles here to save that address so that we can return back to it.
And so it's it's is writing this 800 e somewhere in memory so that we could get back to it.
So let's keep stepping through the code here.
So this point we're in the subroutines.
We read a story a six year 00 which is Port B and we see it right?
The instructions who address six year 00 We have a load, a zero and a story six years or one which is poured a.
And there's the zero being written to six years ago.
We have low day 80 which is the enable bit and a story to six years or one again to set the enable bit.
And there goes we see the top it here getting set to address six years or one, then a load a zero again and another store a six year 01 And there we see the zero going out to six years or one again, clearing that enable bit.
And that's the whole sort of dance we need to go through to send an instruction to the LCD.
Like I talked about in the last video.
And so that all seemed to work just fine.
And so here we are at the next address, you know, 806 f where we're reading 60 hex and 60 is the instruction for return from subroutine.
So what do we expect this to?
D'oh Well, should jump back to that return address that we saved up here, this 800 e.
So we'd actually expected to jump 2800 f, which would be the next instruction to execute after the subroutine.
So it should somehow jump back to this 800 e.
So let's keep stepping forward and see what it does.
So if we step forward and actually he doesn't seem to do anything, it just kind of advances to the next.
You know, After six F, it goes 270 which is just the next memory location, and it's reading, You know, this eight D, which I guess is whatever instruction is there.
I don't think it's actually reading this to execute that instruction.
I think the processors actually is taking a couple extra clock cycles here to do some internal stuff.
So I don't think that just because we see this on the bus that it's it's actually going to try to execute that.
And if we keep going, we see now it actually is reading from address 012 to the next clock cycle.
It reads from 0123 on the 0124 and if you remember that 01230124 That's where this return address was saved up here when we actually jumped into the subroutine.
The processor saved that return address at address 0124 and 0123 So I saved that 800 e.
It's now down here.
It seems like it's trying to get that return address back so that it can return, which makes a ton of sense.
But we've got a problem, and the problem is that it's not reading that same address back that it saved up here.
So it saved this 800 e by writing it to those two addresses.
But down here, when it when it reads from those addresses, it's it's risk reading this a D, and this is our problem.
This is why our program doesn't work, and the problem is that we actually don't have any hardware that responds to address 0123 years or 1 to 4.
There's nothing at this address.
So when the processor rights to address 01240123 up here is not actually writing to anything, it's just sort of throwing it out on the bus, but nothing.
There's no hardware responding to it.
There's no, uh, there's no memory, there's nothing that's that's doing anything with that.
And so then when it tries to read it back down here, is not actually reading from anything.
It's just getting whatever happens to be on the bus, and that's why our program doesn't work.
You know, the process is trying to save the return address, but our computer doesn't actually have any RAM in it yet, so it doesn't work.
And so it gets this far.
But when it tries to return from the subroutine with this return a 060 instruction it tries to read the return address and it gets this eight d a D.
And if we step once more, you can see it jumps to a d a d.
So it thinks it's jumping back to where it should, but it's it's not.
It's jumping to some other just sort of arbitrary address is definitely not gonna be our program anymore, And so if we keep going, it's just gonna do all kinds of other weird, weird, wild things.
But none of this is anything that we want.
One question you might be wondering is, Why is it trying to write the return address to 0124 and 0123?
You know, where did 0124 come from?
Where did 0123 come from?
Where do these addresses come from?
On the data sheet for the processor, it says the stack may use memory from 0100301 f f.
And this is something that's just hard coded into the way that the 65 0 to biker processor works is.
These particular addresses are reserved for this thing called the stack, and the way that a stack works is you've got these 256 addresses from 0100301 FF in memory.
And then you've also got a processor register called the Stack Pointer that is pointing to one of these addresses.
So this is actually gonna be a bit pointer.
But if it has the value of 24 for example, than it would be pointing to this address 0124 because you know all of the stack is gonna be 01 something the stack Pointers is 24 Then it's going to be pointing to 0124 in memory.
And then when we do that jump to subroutine, it pushes the return address on to the stack.
So we're gonna put the return address of 80 zero e onto the stack zero.
But then also, each time it pushes the value onto the stack it decker mints this stack pointer.
Now they're stacked pointers pointing up there that way for inside one subroutine and we want to call another subroutine.
We can push another return address on to the stack and keep going and keep calling all sorts of subroutines.
We could even push our own data onto the stack, you know, as long as we're careful to pull it off the stack in the opposite order that we push it on the stack and actually be really handy place to temporarily stored data.
So, for example, with our subroutine here, Thio sent an instruction to the LCD.
The subroutine starts by.
We actually load something into the air register and then call the subroutine.
And if we look in the subroutine is going to use what's in the air register this one here.
But it's also gonna overwrite what's in a register in order to toggle the enable bit Here.
So something to be aware of is that up here will recall this subroutine.
We're loading something in the air register and then we're calling this a routine.
Well, when that subroutine is done, executing this value is not gonna be in the air register anymore because the subroutine modifies it.
And in our case, that's fine.
You know, we're putting something else in the register right away anyway, so it doesn't doesn't really matter.
But if we wanted to down here in the subroutine, what we could do is push the value in a register onto the stack and then what that would do is it would put that value.
I think it was 38 or something.
Whatever the value was, put that on the stack and then move the and then decadent the stack pointer again.
And so we have this extra value for what are a register was when that when we called that subroutine.
Then, of course, we have to be very careful that before we return from the subroutine, we need to pull that value off the stack and put it back into the A register.
And so what?
This poll a will do is after we've used the A register to toggle the enable flag here, it'll pull a value off the stack, which in this case, is gonna be this 38 is gonna pull that off the stack, and it's gonna increment the stack pointer and then that 38 will get put back into the air register here.
So by pushing a register under the stack at the beginning of the subroutine, pulling it off the stack at the end of the subroutine, that means that when we call the subroutine up here week, when we do this, jump to subroutine.
When we get to the next statement here in our in our program, the A register will have the same thing that it had before we called the subroutine.
If we cared about that, you know, in our case, we don't actually really care about that.
So I'm gonna go ahead and delete both of these lines because they do add a couple extra clock cycles to the program and you know, there's just no need if we push a onto the stack and don't pull it off before the return here.
That would be bad, because instead of returning to the correct address, you know, 800 e If the stack pointers off because we didn't pull a top value off the stack, then the return instruction would think we need to return to zero e 38 So we would end up jumping to some other random memory address, you know, 03 e 38 or whatever.
And of course, that wouldn't be good, cause who knows what's at that address.
So we always have to be careful about not pushing stuff onto the stack that we don't pull off later in the reverse order.
We also have to be careful because on the 65 02 like I said, the stack has always starts it, you know, address 0100 and goes through 01 f f.
So it's just 256 bites.
So if we end up pushing more than 256 bites onto the stack, you know, calling Maur function.
So you know, we call this function.
Maybe we pushed this value on the stack.
And maybe within this function, we call another function.
And so we have to, you know, push another address onto the stack, and maybe that function calls another function.
Yeah, if we if we got convoluted enough, or maybe we had some records or something, we could get in a situation where, you know, we keep pushing more and more stuff onto the stack.
And, of course, we get up to 0100 And then if we were to deck Ament the stack pointer, we just wrap around two FF and so we'd end up down here at 01 f f.
And we could keep pushing stuff onto the stack.
Um, and all of this would be fine up until the point where the stack point of wraps around and we're about to push something onto the stack.
That overwrites stuff we already had on the stack.
If that happens, that's not good.
That's what we call a stack overflow.
Because here, you know, if we ended up pushing something else onto the stack, maybe there's another function call you.
We've clone gone down the rabbit hole here, and we have another function call.
We push that return address on to the stack.
It would overwrite this return address and everything would seem to be working fine, because we would return from that function and we would return from the right address and we returned from whatever called it and we'd pull off whatever else we've had on the stack, and it would just kind of keep wrapping around.
We got to f f and then when it incriminated, we pulled something else off the stack.
It would rap back around to AA 00 and we could keep pulling stuff off the stack.
All of this would work fine until we get to this point where we've overwritten stuff from that very first function call.
And so when we returned from that very first function call, we wouldn't have the correct return address here, and that would be bad.
And you know who knows what would happen.
So we definitely want to be careful to avoid a stack overflow situation like this because they could be very difficult.
Thio troubleshoot.
You might also be wondering you, we've got this stack that goes from 0100301 f f you know Why do we start pushing data at 0124?
You know where?
Why with stack point or start here on and not you know anywhere else.
Well, because the stack point I just happened to start out to four just happen to be initialized with that value.
It's It's just completely arbitrary when the CPU powers on what value is gonna be in that stack pointer.
So maybe a good thing to do would be at the beginning of our programmer, right?
When the CPU powers up in Initialize is, you know, it initialize the stack pointer toe ff here, so it always starts out at the top of the address range.
And then the nice thing about that is that as we push values onto the stack, we always look at the stack pointer and no, like, Oh, if it's pointing to stop owners to six pointing here, then that means that we would have 2026 but to six hex.
However, however many bites, that is, we would know how many bites we have left in our stack, whereas if it's down here, you know f d.
We know we have 254 bites available above that in our stack.
So how we go about putting FF into the stack pointer to initialize it like that?
Well, we look at our instructions, you know, there's a load, a instruction, There's a load.
Ex instruction.
There's a load.
But unfortunately, there's no load stack or load stack pointer instruction.
However there is.
If we flip over here, there are some transfer instructions that work with the stack pointer.
So, for example, we have ah, transfer.
Where is it?
Transfer X transfer X to stack.
This will let us put a value in the stack pointer from the ex register, which we can load.
So basically, this has to be a two step operation.
First week unload X with ff and then transfer ext s.
And this will end up initializing the stack pointer to F F because we're loading that into X.
And they were transferring that to this.
The stack pointer metal initialized the stack pointer toe FF, meaning the stack point or now starts at the end here.
And then as data gets pushed onto the stack, the stock pointer moves up like this.
Although usually say the stack grows downward because the numbers are actually getting smaller.
S o you know, maybe 100 this upside down, but you get the idea.
But anyway, you know, the reason our program isn't working is because it's trying to use this stack, but our computer doesn't actually have any RAM in it.
At address.
01230124 or you know 01 f f o r.
Anywhere, and that's what I plan to fix in the next video, we'll add some memory to the computer by wiring up this Ram chip.
And, you know, right now, of course, we have the rahm, which has read only memory, and that's where our program is stored.
But so far we haven't added any ram that we can read from and also right to, and so that's what we need to d'oh.
So the next video, I'll add the Ram and make sure part of it is accessible that those address 0100301 f f and then our computer will have memory.
We can write, too, which means it'll have a stack, which means that subroutine calls will work.
In the meantime, you can check out my Web site at eater dot net slash 65 02 for more information.
Schematics on kits which include all the parts that I've used in these videos in case you're interested in following along yourself and, as always, thanks to all my patrons for your support, your contributions on patri on really help me continue making this content, so thank you.


What is a stack and how does it work? — 6502 part 5

14 タグ追加 保存
林宜悉 2020 年 3 月 28 日 に公開
  1. 1. クリック一つで単語を検索


  2. 2. リピート機能


  3. 3. ショートカット


  4. 4. 字幕の表示/非表示


  5. 5. 動画をブログ等でシェア


  6. 6. 全画面再生


  1. クイズ付き動画


  1. クリックしてメモを表示

  1. UrbanDictionary 俚語字典整合查詢。一般字典查詢不到你滿意的解譯,不妨使用「俚語字典」,或許會讓你有滿意的答案喔