======DAPX======
=====stack operations=====
====PUSH====
In assembly language, "push" is an operation that stores a value onto the stack. It involves copying the value in question out of the indicated register and placed into the current top of stack position (the address stored in **SP**) and then decreasing the memory address in the stack pointer register (**SP**). The top of the stack is always referencing the next memory address beyond the last piece of data placed on it. This operation is commonly used to save the current state of a register or backup temporary data before modifying the register. Subroutine parameters are handled via items added onto the stack just before **CALL**ing.
NOTE: a **CALL** is basically a combined **PUSH** and **JMP**. The current address of the instruction being processed is what is pushed onto the stack before redirecting execution there. This is how the computer knows how to get back here when we ultimately **RET**urn from a called subroutine.
Make sure not to push too many values to the stack, as this can cause a **STACK OVERFLOW**, which is when the values in the stack start overlapping with the program data. This can cause errors as the program tries to read the now corrupted data.
Similarly, make sure all stack transactions are balanced: ie for every PUSH there is an eventual encountered POP. POPping more than PUSHing can result in a **STACK UNDERFLOW**.
====POP====
On the other hand, "***POP**" is the inverse operation: retrieving a value (the one most recently **PUSH**ed) from the stack. It involves first incrementing the memory address stored in the Stack Pointer (SP), then copying the value from that location into a register or memory location. Popping is frequently employed to restore saved values after returning from a subroutine or to retrieve previously stored data during program execution. You must POP the same amount of times you PUSH or you risk having an error (such as a Stack Underflow, in the case of more POPs than PUSHes), as the stack relies on that balance for many aspects of the system to function.
====referencing relative to SP/BP====
* BP is the base of your stack.
* SP is the current position of your stack.
BP starts at the end of memory. So when pushing something onto the stack you are essentially doing BP-1. This value would be what SP is. If you push 7 times then SP=BP-7.
You can change where the BP is by doing this for example:
push BP
mov BP, SP
This creates a "local stack", which you can use as a separate stack to push and pop values from without disturbing any previously pushed values.
If you had values at the bottom of the stack you could do this to retrieve them:
mov R0, [BP+2]
To get back to your previous stack, you just need to reverse the code above:
mov SP, BP
pop BP
=====Preserving our Registers=====
When incorporating debug.s it will use the same everything as what our main game is using, so the same Registers and Memory Addresses. So we want to save our Registers before debug.s messes with them. To do this we would push our registers:
push R0
push R1
push R2
...
push R12
push R13
Then after we have done everything we need to do in debug.s we need to load our saved values from the Registers so that when we go back to our main code it looks like we never left. So we pop them in reverse order:
pop R13
pop R12
...
pop R2
pop R1
pop R0
ret
=====Preserving our Textures=====
When making our debug function, we want it to strictly print out text and nothing else, if it were to do anything else than print out the text this can be perceived as an error and something that needs to be fixed. In order to preserve our texture we need to push it onto the stack before we execute the debug and pop it off the stack after debug has finished its run.
in R0, GPU_SelectedTexture
Push R0
in R0, GPU_SelectedRegion
PUSH R0
POP R0
out GPU_SelectedRegion, R0
POP R0
out GPU_SelectedTexture, R0
Remember Pop in the reverse order of what you pushed and to push with a register you have already pushed to the stack in order to preserve that register as well. In this example you should push R0, and after the last out POP R0, to fully preserve the texture/registers.
Now that you have preserved the registers and textures you are free to change them as you please. Remember that the BIOS texture that you need to use to print out the ascii text for all your outputs is -1.
out GPU_SelectedTexture, -1
And now that you are preserving your textures, it will go back to whatever you had before calling _debug. This can be extremely helpful if you are only using one texture for the whole game.
=====bitwise operations=====
====OR====
A B | X
-------
0 0 | 0
0 1 | 1
1 0 | 1
1 1 | 1
====AND====
A B | X
-------
0 0 | 0
1 0 | 0
0 1 | 0
1 1 | 1
====NOT====
A | X
-----
1 | 0
0 | 1
====NAND====
A B | X
-------
0 0 | 1
0 1 | 1
1 0 | 1
1 1 | 0
====NOR====
A B | X
-------
0 0 | 1
0 1 | 0
1 0 | 0
1 1 | 0
====XOR====
A B | X
-------
0 0 | 0
0 1 | 1
1 0 | 1
1 1 | 0
====XNOR====
A B | X
-------
0 0 | 1
0 1 | 0
1 0 | 0
1 1 | 1
*Notice how similar this performs when compared to "ieq"
====SHIFT====
===Shift Right====
A|X
---------
1000|0100
0100|0010
0010|0001
0001|0000
===Shift Left===
A|X
---------
0001|0010
0010|0100
0100|1000
1000|0000
**Note:** When shifting a value to the right/left, the bit that gets added to the beginning/end is always a 0. This means once you shift a bit containing a 1 out of range, you can not get it back.
You will want to use a register to shift (see the bitwise example in the public directory). The shift is used to keep track of the number of bits to shift the mask when extracting each nibble from the value when converting the variable to hexadecimal. It ensures that the correct number of bits are extracted from the variable during each iteration of the loop subroutine used for conversion. The 28th bit is the most significant nibble. To go to the next nibble, shift right 4 (shift left -4).
To extract the digits of a two-digit number (e.g.,10, 11, 12, etc.), you can use division by 10 to find the quotient (tens digit) and modulus operation to find the remainder (ones digit).
=====pseudocode=====
====debug====
SET DATA
SET X
SET Y
SET POSITION TO LEFTMOST NIBBLE
UNTIL WE HAVE DISPLAYED ALL NIBBLES:
OBTAIN NIBBLE AT POSITION
SHOULD NIBBLE BE GREATER THAN OR EQUAL TO TEN:
ADD SEVEN TO ITS VALUE
ADD FORTY EIGHT TO ITS VALUE
DISPLAY NIBBLE AT X, Y
INCREMENT X ACCORDINGLY
ADJUST POSITION TO NEXT NIBBLE TO THE RIGHT
REPEAT
In your code you will want to put
%include "debug.s"
at the very end of your code. THIS IS VERY IMPORTANT. If you include debug.s at the top of your code, the hexadecimal values may print but the remainder of the program in which you called __debug may not function as expected.
=====debug function=====
===Formatting===
Beyond getting your values to be displayed you also need to include "0x" to signify that the number you are displaying in hexadecimal. It is important to know that the 0 and x have a number assigned to them in ASCII code which are as follows:
ASCII code for 0 --> decimal 48, hexadecimal 0x30
ASCII code for x --> 120
ASCII code for [ --> 92
ASCII code for ] --> 93
ASCII code for : --> 58
Knowing this, we can now select where we want to display these values by simply calling the following:
mov R0, 48 ; ASCII code for 0 is moved into RO
out GPU_SelectedRegion, R0 ; Selecting region
out GPU_DrawingPointX, R2 ; Providing X axis location
out GPU_DrawingPointY, R3 ; Providing Y axis location
out GPU_Command, GPUCommand_DrawRegion ; Displaying
iadd R2, 9 ; Spacing logic
The only thing that really needs to be explained is the last line. For me, R2 is keeping track of the spacing logic that keeps the numbers from printing on top of each other.
This has now only printed the 0. We still need to print the x. The process is identical to printing 0 but this time instead of giving 48 to R0 we do 120 instead.
=====Getting your value=====
Now if you wanted to print the value inside your Register it will be a bit different. First off you would have to Mask and Shift your value.
====Masking====
* Masking is specifying what value you want. Say your value is "Deadbeef" and you want only the D. To do this you would create a mask where the D equivalent is the only thing.
mov R4, 0xF0000000
* That would be your MASK value for D. To actually get the D you would:
and R0, R4
* And now R0 holds only "D".
====Shifting====
* You know would want to shift your value so that the D is in the one's place. 0x0000000(0) The 0 in () is the ones place. To do this you would want to shift your value by 28 since each value is 4 and the D is all the way to the left. But since Vircon only knows one direction we need to flip our value and then shift it:
mov R5, 28
mov R1, R5
not R1
iadd R1, 1
shl R0, R1
To get the next value ("e"), you will need to shift the mask to the right as well. Since each value is 4 bits, shifting the mask is much simpler:
shl R4, -4
====To ASCII====
Since we are working in hex and we need to convert to ascii we would usually add 48 to our value to get it's ascii equivalent, but that is with decimal, hex is slightly different. In hex, the values we know in decimal as 10-15 are now A-F.
* So if dealing with 10+ we need to add 7 onto of the 48 we are already adding.
* Otherwise, if the value is < 10, then we add 48 like we normally would.
* And then just repeat what we did before but change your Shift and Mask values as need be.
Why +7? Take a look at the arrangement of values in the ASCII table:
^ symbol ^ decimal ^ hexadecimal |
| ’0’ | 48 | 0x30 |
| ’1’ | 49 | 0x31 |
| ’2’ | 50 | 0x32 |
| ’3’ | 51 | 0x33 |
| ’4’ | 52 | 0x34 |
| ’5’ | 53 | 0x35 |
| ’6’ | 54 | 0x36 |
| ’7’ | 55 | 0x37 |
| ’8’ | 56 | 0x38 |
| ’9’ | 57 | 0x39 |
| ’:’ | 58 | 0x3A |
| ’;’ | 59 | 0x3B |
| ’<’ | 60 | 0x3C |
| ’=’ | 61 | 0x3D |
| ’>’ | 62 | 0x3E |
| ’?’ | 63 | 0x3F |
| ’@’ | 64 | 0x40 |
| ’A’ | 65 | 0x41 |
| ’B’ | 66 | 0x42 |
Notice how 0xA in hex (decimal 10), when we add 48/0x30 to it, would be 48+10=58 / 0x30+0xA = 0x3A, the ’:’. But if we add an additional 7 to it (58+7=65; 0x3A+7=0x41) we arrive at the desired ’A’.
======debugmemory function=======
NOTE: One of the requirements is to call _debug to display information while using debugmemory in the same debug.s file. The setup will be somewhat similar to _debug.
Even though we are calling _debug you'll still want to push everything at the start and pop everything at the end of _debugmemory.
Here are some tips (feel free to edit with more information):
* First you will want to set up. This would include doing the same setup for _debug but you are manually setting the X and Y values (0,0 is recommended) and will be using [BP+ some value] to get your two parameters.
* Then you'll want to create a loop.
* You'll want a way to know when parameter one igt parameter two.
* Keep in mind _debug works with calling stuff from the stack so you might have to update the stack like you would when calling it from your game.
* Don't forget to update your X, Y, and parameter one!
===Displaying===
When considering the output we see the following:
[0x200001A6]: 0x48656C6C
We see a hex value in brackets, which is the memory address, and the hex value that is not in brackets which is the value stored at that given memory address.
In dap0 we could get the value given some register but how do we get the memory address? Well, you can actually use your debug.s function! To display the memory address simply copy (or do something similar) to the following example:
; Push the parameters for the _debug subroutine to display memory
push R1 ; Memory address
push R3 ; X coordinate
push R4 ; Y coordinate
; Call _debug to display the memory address
call _debug
pop R4 ; Y coordinate
pop R3 ; X coordinate
pop R6 ; Memory address
The only thing you need to note is how we push the register and take a mental note of how we did it. Lets not display the value at that given memory address:
; Push the parameters for the _debug subroutine to display value at memory address
mov R6, [R1] ; Putting value in R6
push R6 ; Value
push R5 ; X coordinate
push R4 ; Y coordinate
call _debug
pop R4 ; Y coordinate
pop R5 ; X coordinate
pop R6 ; value
Notice how in displaying the value at a given memory address we put brackets around the register and then move that into another register (R6 in this case) and then push R6 instead of the original R1.
======debugregister function======
=====dapX imagery=====
====dap0====
{{:notes:comporg:spring2024:projects:1.jpg?400|}}
{{:notes:comporg:spring2024:projects:2.jpg?400|}}
====dap1====
{{:notes:comporg:spring2024:projects:3.jpg?400|}}
{{:notes:comporg:spring2024:projects:4.jpg?400|}}
====dap2====
{{:notes:comporg:spring2024:projects:5.jpg?400|}}
{{:notes:comporg:spring2024:projects:6.jpg?400|}}
=====3.7.24 example=====
How to add "without adding"
0110
0101
----
&:0100 <- carry
^:0011 <- sum
---------------------
0100 SHL 1
|
v
1000 <-incoming carry
0011 <- previous sum
----
&:0000
^:1011 <- This is the answer
---------------------
How to replicate "ile" comparison:
X <- A xnor B
Y <- A xor B
Z <- X ior* Y
*ior - "inclusive or"
======Debug Registers======
===Printing out R:===
To print the registers you have to figure out how to print out the R, the :, and the number that goes along with it. In this it is important to note that the ascii value for R is 82, and the ascii value for : is 58. Thus printing these out sequentially will look something like this. Remember to have your texture set to -1 so the text shows up.
mov R0, 82 ; printing R
out GPU_SelectedRegion, R0
out GPU_DrawingPointX, R1
out GPU_DrawingPointY, R2
out GPU_Command, GPUCommand_DrawRegion
iadd R1, 10
;Print out the number here
mov R0, 58 ; printing :
out GPU_SelectedRegion, R0
out GPU_DrawingPointX, R1; X value
out GPU_DrawingPointY, R2; Y value
out GPU_Command, GPUCommand_DrawRegion
To print out the number in the middle, we first need to check if the number is greater than 10. If it is then we print out a 1 then subtract 10 from it, this is so we can do a ascii shift with the second number that changes it to the number we want to print.
;R5 holds the value we want to print.
mov R1, R5
mov R2, R5
ige R2, 10
jf R2, _less_than_10
;printing out a 1
mov R0, 49
out GPU_SelectedRegion, R0
out GPU_DrawingPointX, R3
out GPU_DrawingPointY, R4
out GPU_Command, GPUCommand_DrawRegion
iadd R3, 10
isub R1, 10 ;we subtract 10 as 1 has already been printed
_less_than_10:
iadd R1, 48 ;ascii shift by 48 to print out the number
out GPU_SelectedRegion, R1
out GPU_DrawingPointX, R3
out GPU_DrawingPointY, R4
out GPU_Command, GPUCommand_DrawRegion
The last step is to put both of these together in a loop, remember to change the X/Y at the end of the loop.
===Printing registers===
===Debugging our debugregs Subroutine===
Trying to get your debugregs to have the correct output is challenging, but by far the best way to see if you have the correct output is by tossing in some random values into each register that you display to see if you have the correct output. Here is an example of that.
mov R0, 0x00000000
mov R1, 0x00000001
mov R2, 0x00000002
mov R3, 0x00000003
mov R4, 0x00000004
mov R5, 0x00000005
You repeat this above process for registers from 0 to 13. It is important to note that you do this right before you call the subroutine so the values that you just put in don't get wiped out from your previous ASM code.
===Calling debugregs===
As we have worked on these dap projects, the process of calling such debug subroutines has become simpler. With dap0, we had to have the following in our program:
mov TMP, R8
push TMP
mov TMP, 100
push TMP
mov TMP, 200
push TMP
call _debug
Then later after the hlt, you'll always have the %include "debug.s" for all the dap projects.
Concerning dap2, we have drastically simplified the process of debugging. All that is need is just one line, Not accounting for the include line. Put the following to use your debug registers subroutine:
call _debugregs