|
|
This post was updated on .
Hello everyone,
So I have been trying to do the second program on chapter 4 and I am stuck. This is what I have come up with so far:
(deleted)
This only turns on and off the first 16 bit word on the first row. I have not been able to do it to the whole screen. My idea is, in the (BLACK) part, as long as a key is pressed, it would keep turning on the next 16 word on the row until the whole screen is black. However, I have not been able to translate it into code.
Could someone help me and give me some ideas? Should I just manually add all the rows and do it that way? It would take forever.
Thank you in advance.
|
Administrator
|
Walk carefully through your code by hand and see what it does. Every time you jump to WHITE or BLACK you change ONLY the 16-bits associated with RAM address 16384 (the value of SCREEN).
There are lots of ways to tackle this. Keep in mind that the requires only require that if you hold down the key "long enough" that the screen will become all black and that if the key is kept released "long enough" that the screen will become all white. There is NO requirement that it be particularly responsive immediately after pressing or releasing the key.
|
|
I have read and reread this chapter like 30 times, and at some point I realized the authors were trying to point me in the right direction by making that "long enough" comment.
I thought about this a lot and I think this is the correct approach:
1) Start at register 16384.
2) Turn every bit in the register on.
3) Add one to the register address.
4) Go back to 2 and rinse and repeat, until the whole screen is black.
The problem is that I can't translate this into code because we can only work with two registers, and the D register always holds the value of the keyboard.
@16384
M=-1
A=A+1
D=A
@D
M=-1
// go back to the top
(I have been trying the variations of the code above. I get that D register can't hold an address, but even if I do @A, it stilll doesn't work.)
I am not sure how I can add to 1 to the register and then set that register's value to -1, if that makes sense.
|
Administrator
|
You have sixteen thousand registers (or memory locations) to help.
Can you write a snippet of code that takes the value stored in, say, R3 and uses that value as the address at which to write the value 0 (or -1).
Can you write a snippet of code that takes the value stored in, say, R3 and makes the value stored there one greater than it was.
Can you write a snipped of code that takes the value stored in, say, R3 and checks if it is equal to some specific value and, if so, jumps to a particular label?
Figure out how to do each of these. Then figure out how to combine them to fill entire screen with either a 0 or a -1.
|
|
This post was updated on .
I did what you suggested, watched the videos by the authors and read about pointers on this forum, and I have made some progress. For some reason I overlooked that doing
A="Something"
M=-1
would set the value of "Something" register to -1.
(deleted)
Above code writes two rows as long as a key is pressed and deletes those rows as long as nothing is pressed.
I have been banging my head against the wall trying to figure out how to extend this to the full screen. The problem is, in the (BLACK) part, whenever I return back to the @SCREEN, A register resets to 16384.
I can keep track of the address with D register, but D register is used to check if a key is pressed or not.
How do I get out of this? This problem is starting to get under my skin. :)
|
Administrator
|
Forget about the keyboard or the screen memory.
Write an assembly language program that does the following.
Sets R13 to some value (say 5) and in a loop increments the value stored in R13 until it is equal to some larger value (say 10).
Get just that much working. Don't worry about anything else.
|
|
Done.
@5
D=A
@13
M=D
(LOOP)
@13
M=M+1
@10
D=A
@13
D=D-M
@END
D;JEQ
@LOOP
0;JMP
(END)
Now what?
I have got an idea:
1) Whenever a key is pressed, the program jumps to (BLACK) loop.
2) It turns on every bit in the register.
3) It keeps going one register at a time until it hits the register 24575.
4) It exits the loops and checks to see if a key is still pressed.
Is this a good idea?
|
Administrator
|
kingofbuffs wrote
I have got an idea:
1) Whenever a key is pressed, the program jumps to (BLACK) loop.
2) It turns on every bit in the register.
3) It keeps going one register at a time until it hits the end.
4) It exits the loops and checks to see if a key is still pressed.
Is this a good idea?
That is perfectly acceptable, but you need to finish it out that if a key is NOT pressed when it checks, it does the same thing only turns off every bit in each memory location.
You don't need a separate BLACK and WHITE loop -- what if instead of setting the register to -1 in one loop and 0 in the other, you simply set each register to the value stored in some specific register (say R14) and when you check for whether a key is pressed you simply set the value in R14 accordingly and then make start the loop?
|
|
This post was updated on .
Alright, so I threw everything but the kitchen sink at the text editor and got this:
(deleted)
I didn't test this until the end (it takes forever), but I do believe it works. Whenever a key is pressed, the program starts writing lines, and if the key is released, it erases those lines.
There are two problems as far as I can tell:
1) If the program was left running without a key being pressed, it would just keep decrementing the value in the register 14. I don't know what would happen if it reached -30k-something.
Moreover, the longer it is left like that, the longer it takes to start writing the lines after a key is pressed. (Since it takes a while to increment back up to 16384)
2) I am not quite sure what happens when it reaches the end of the screen.
Is this good enough or should I fix these things?
|
Administrator
|
You should definitely fix those things. The point of the exercise is for you to develop the ability to write a program in assembly language that does a particular task properly. Part of doing it properly is to not overwrite memory that is potentially being used for some other reason.
Break the problem down into small, manageable pieces.
The first thing you need to do is get a loop that goes from 16384 up through and including 24575 and stops.
This is a problem that you've already solved. Recall, "Sets R13 to some value (say 5) and in a loop increments the value stored in R13 until it is equal to some larger value (say 10)."
Well just change that to, "Sets R13 to some value (say 16384) and in a loop increments the value stored in R13 until it is equal to some larger value (say 24575)."
It will actually be a bit easier if that is tweaked to "... until it is larger than some larger value."
Once you have that, then we can move on to the next piece, which is embedding that loop in a larger loop.
As for the simulation speed, that is because you are not adjusting the simulation animation. Set the speed slider all the way to the right (to "Fast") and in the "Animate" box select "No animation" and be sure to select "Screen" in the "View" box.
|
|
I did it!
I checked with the automatic test, and also I checked manually, it seems to be working just fine. It doesn't throw any errors at any point. (If it's okay I'd like to pm you the final code for final confirmation.)
Thank you so much for your help. I wouldn't be able to do it without it.
|
Administrator
|
Sure, go ahead and send me the code.
If you are interested, a good exercise is to see how few instructions you can get your code down to. The best I've been able to do is 18 instructions, though I suspect it can be done in fewer.
|
Administrator
|
I got your e-mail with your code.
I was able to reverse engineer it and it seems like it should work okay, but it has some real inefficiencies in it. Your code totals out at 44 instructions -- a pretty brute force approach can be done for about 27 instructions and a tight implementation can be done in 18.
One thing that you want to get in the habit of doing is really providing good comments in assembly language code. The level of detail inherent in assembly code is just too low for humans to be able to easily recognize the why of the code. The comments provide that information.
One common approach, which works pretty well for a number of reasons, is to first write out your algorithm in a high-level language, particularly one like C if you happen to know it, and then use those as your comments, putting the assembly code that performs each statement underneath that comment. But in addition to making good code comments, humans think at the level of a high-level language much better than at assembly (that's precisely the reason that high-level languages were invented). So it will probably take you less time to develop a sound algorithm that way and then you just need to worry about how to implement each step of the algorithm in assembly code, which is sufficiently constrained so as to make that pretty simple -- in fact you will be automating just that process in your compiler and translator.
Using the higher-level code as comments is also something to strongly consider carrying over into your compiler and VM translator -- use the source code to provide comments in the output code. It will make debugging your tools chain a LOT easier.
Here are some things to chew on:
Consider the following code:
(BEGIN)
@KBD
D=M
@BEGIN
D;JEQ
@SET
D;JNE
(SET)
@16383
D=A
@13
M=D
It appears that the purpose of this code is to wait until some key press is detected and, once it is, to continue on by first setting R13 to 16383. So don't make the reader guess that this is the purpose, tell them that it is.
Then, if the first jump isn't taken, you know that the second jump should be taken -- so don't risk messing up and not covering all the bases -- which could happen if you were to do JGT for the first and then use JLT for the second, forgetting about the case of equality. What you are implementing is an if-else logic structure, so make the else unconditional.
But in this case you don't need the second jump at all because if you don't do the first jump and get rid of the second jump, the code will naturally fall through to the instruction pointed to by (SET) anyway.
Finally, whenever you have "magic numbers", explain why they are there. What is special about 16383? The reason you are using it is because it is one less than SCREEN. You should use SCREEN then, because what happens if someone assembles your program for a platform in which the screen memory is located somewhere else?
Taking all of this into account, consider something like the following:
// wait until a key is pressed
( WAIT_FOR_KEY_DOWN )
@ KBD
D = M
@ WAIT_FOR_KEY_DOWN
D; JEQ
// R13 = SCREEN - 1
@ SCREEN
D = A-1
@ 13
M = D
Choosing good label names makes the code a lot more self-documenting, too. In this case, had I not included the first comment, it would be been pretty easy to determine what the purpose of the first block of code was. But the comment is still very valuable because it makes it clear WHAT the code is SUPPOSED to accomplish, while the well-chosen label makes it pretty obvious HOW the code accomplishes what it is supposed to do -- and also makes it a lot easier to figure out why the code isn't doing what it is supposed to do when there is a bug in it.
If you wanted to use a high-level code construct instead of a prose comment, you could have used something like
// while(!keyPressed());
|
|
How do you do that in just 18 instruction?
I've been boiling down my fill.asm solution from an initial soup of 70 instruction to 56, 52, 28, 25, 23, and no finally 19 instruction. I don't even have bones left!
So how do I remove one more instruction without breaking the whole program?
Edit: Forget it! I just found the solution. 18 instructions! You know, that was an extremely frustrating problem to solve
|
Administrator
|
Good job. Frustrating, but probably very educational. Most things worth learning require effort.
|
|
Thanks, but this isn't my fist dive into assembler coding. I've been down that rabbit hole following Ben Eater's YouTube series. Having just two instruction, Addressing and Computing, seems quite limiting, though having brewed this solution down to the atoms, and brewing Multi.asm Egyptian style, I can see how powerful the Computing instruction actually is.
The only thing you cannot solve with it is the Halting problem
|
Administrator
|
Actually, there's lots of problems that it could not solve, the Halting Problem being one of the classic examples of a class of problems that are "undecidable". The question of decidability is a very interesting one. There are, of course, problems that we know are decidable and problems that we know aren't. There are plenty of problems that we don't know whether they are decidable or not. None of that is shocking. But I believe (haven't been able to track down an example) there are problems that we have been able to prove that we can't prove whether or not they are decidable.
|
|
Question: When you say 18 instructions, do you count from 0 or from 1?
I just realized that I've using the number of the final instruction as when loaded into the CPU Emulator, and it counts from 0…
|
Administrator
|
Eighteen instructions (at addresses 0 through 17).
|
|
WBahn wrote
Eighteen instructions (at addresses 0 through 17).
To be honest, I would like to see that solution. I just too another look at the project, but although I tried to cough up a different solution, I still ended up with 19 instruction (0-18).
I can't move the blocks of code around, as that only results in adding extra code, and I can't delete any lines of code, as that breaks the functionality of the program.
And I need all the lines of code I have:
* Setup: 4 instructions
* Reading keyboard: 2 instructions
* Setting the color: 3 instructions
* Painting the current pixel: 3 instructions
* Looping through the screen: 5 instructions
* Running the program in an endless loop: 2 instructions
|
|