Constructing IF statements can be very simple. The trick of dealing with complex statements is to do a bit of maths on them and turn them into simple ones. I'm going to concentrate to start with on the branching instructions ibeq, which branches if two (integer) registers are equal, ibne which branches if they aren't, and b, which always branches.
About the simplest IF statement we could do is to check if the value in a register is zero, because we can do it in one instruction -
if (iCheck == 0)
{Do this}
becomes
ibeq iCheck, vi00, DO_THIS:
b DONE:
DO_THIS:
;This code is only executed if iCheck==0
DONE:
;This code is executed either way.
Notice that we need to use 2 labels to implement the code, one to tell the VU where to go if iCheck is zero, but another to tell the VU where to skip to if iCheck is not zero. Shuffling the instructions around eliminates the need for the second label
ibne iCheck, vi00, DONE:
;We can only get here if iCheck==0
DONE:
;This code is executed either way.
Logically, this code does exactly the same thing but with one less instruction and one less label. Not much of a saving (In fact, eliminating the use of a label saves you nothing memory wise) and makes your code a little harder to read, but the choice is yours.
Adding an ELSE statement isn't too hard either, by changing the label that the unconditional branch jumps to
if (iCheck == 0)
{Do this}
else
{Do that}
becomes
ibeq iCheck, vi00, DO_THIS:
b DO_THAT:
DO_THIS:
;This code is only executed if iCheck==0
b DONE:
DO_THAT:
;This code is only executed if iCheck!=0
DONE:
;This code is executed either way.
Notice that I've actually used 3 labels here, one for the iCheck==0 code, one for the iCheck!=0 code, and one for the end of the statement. I could again reverse the logic at the top and save myself an instruction
ibne iCheck, vi00, DO_THAT:
;This code is only executed if iCheck==0
b DONE:
DO_THAT:
;This code is only executed if iCheck!=0
DONE:
;This code is executed either way.
So, having been given the code to test whether a register equals zero or not, you can quite easily adapt it to see if a register equals any other constant value, or if 2 registers are equal, just by doing a little maths. All you have to notice is that mathematically, A==5 means precisely the same as A - 5 == 0. So, if you want to check whether iCheck==5 or not, you simply subtract 5 from it, and now check whether iCheck == 0. Something like this:
isubiu iCheck, iCheck, 5
ibeq iCheck, vi00, DO_THIS:
b DO_THAT:
DO_THIS:
;This code is only executed if iCheck==5
b DONE:
DO_THAT:
;This code is only executed if iCheck!=5
DONE:
;This code is executed either way.
Unfortunately, the above code destroys whatever value you had in iCheck. To get round this, you could either add 5 back on to the register after the ibeq line, or if you have a register to spare, you could store the result in a temporary register and check that. This is the fun thing about programming, there are so many ways to express the same idea, each with their own drawbacks and advantages.
isubiu iTemp, iCheck, 5
ibeq iTemp, vi00, DO_THIS:
b DO_THAT:
DO_THIS:
;This code is only executed if iCheck==5
b DONE:
DO_THAT:
;This code is only executed if iCheck!=5
DONE:
;This code is executed either way.
There are a few more branching instructions I haven't used yet, ibgtz, ibgez, ibltz, and iblez. These are the instructions you would need to test inequalities. Notice that unlike ibeq and ibne, we aren't testing the relationship between two registers, we're testing the relationship between one register and zero. Maths comes to help us here again, remember that A<B is logically the same as A-B<0, and we're off. By means of an example, this piece of code checks to see if the A register is less than the B register.
isub iTemp, iA, iB
ibltz iTemp, A_LESS_THAN_B:
A_NOT_LESS_THAN_B:
;Some code here
b DONE:
A_LESS_THAN_B:
;Some code here
DONE:
Calling Functions / Subroutines
There are a few more branching instructions, bal, jr and jalr, and they are useful to create other kinds of jumps. bal and jr used in combination make something like a GOSUB/RETURN pair if you've ever programmed in basic.
Let's say we had a little section of code that we executed a lot of times at different points in the code, say one that did a matrix multiply. We have two options to code up that repeated section, we either use macros, or we create a subroutine. Macros are a little more versatile, but use up more instruction memory, whereas subroutines use less instruction memory, but are a tiny bit slower, due to the branching in and out of the code.
Anyway, when we want to call the subroutine, we use the bal instruction
bal iJump, MATRIX_MULTIPLY
The bal instruction branches to the MATRIX_MULTIPLY section unconditionally, but before it does so it stores the address of the instruction that would have been executed next in the iJump register. Then, somewhere else in the code, we have the subroutine
MATRIX_MULTIPLY:
;Code goes here - see 7: Some Maths
jr iJump
Which, thanks to the jr command, jumps back to the address stored in the iJump register, i.e. back where we made the subroutine call from. That's the essence of subroutines, you can make them into functions by making the subroutine operate on particular registers, setting those registers before the bal in the main code, and reading them again after the function has returned.
For example, let's make a really simple function that adds up vi01, vi02, vi03 and vi04, and stores the result in vi05. This is a completely unrealistic example, I have no idea why you'd want to do this, but the theory is still sound :)
Then, in our main code, all we have to do is write
bal iJump, FUNCTION_4_SUM
obviously after making sure that some meaningful values lie in vi01-04. Then the sum of the 4 registers magically appears in vi05. Like I said, this is a totally fictitious example, if you have a better, genuinely useful one, mail it to me!
Loopy Loops
Believe it or not, all of your favourite looping constructs come right back to the humble if statement. However, there is a little extra part to doing loops in VCL that helps optimisation. I'm going to concentrate of FOR loops here, you should quite easily be able to navigate your way around DO-WHILE and REPEAT-UNTIL loops using the information on if statements above.
From a C background, for loops have 4 parts.
for(Set up a variable; Do a check; Do something every time we go round the loop)
{
Do something inside the loop.
}
We usually see counter variables being used like this
for(i=0; i<10; i++)
{
This piece of code executes 10 times.
}
However, if we aren't using the value of i anywhere in the loop, we can change the logic around to get rid of that extra isubiu in there.
for (i=10; i!=0; i--)
{
This code still executes 10 times
}
iaddiu iCounter, vi00, 10
TOP_OF_LOOP:
;This code still executes 10 times
isubiu iCounter, iCounter, 1
ibne iCounter, vi00, TOP_OF_LOOP
There, same instruction flow, but with one less instruction per loop. Come on, you love assembly now, don't you?
As I promised earlier, there is something that VCL can do to help with optimising loops. There is an optimisation technique called loop unrolling. Basically, if all you're trying to do is repeat an instruction 10 times, it's quicker to write the instruction 10 times than it is to bother with decrementing a counter, checking to see if it's zero yet then jumping back to the start of the loop. However, to save your fingers, VCL can do even better unrolling for you, using the --LoopCS directive. At the top of your loop, you put the line
--LoopCS n,m
where n is the minimum number of times the loop will be executed, and m is the number of times you can safely overrun the loop. What, overrunning a loop, isn't that insane? Well, it turns out that VCL can produce better code if you allow it to run the loop more times than you need. Say you're writing to an array that's 15 units long, if you allocated 17 units, you could safely run the loop 2 times to many without a problem. How does VCL produce better code with this? I haven't a clue. In fact, as of it's latest release, the m parameter is ignored, so it really doesn't matter!
Homework
And that, as they say, is that. I know I promised you some good homework questions in this lesson, but I decided to publish the lesson before completing the homework questions so you can read and digest this first. I guess the competition deadline approaching is making me a little sloppy in what I release... but if it helps you get your demos rolling in, then all the better!