Before you attend this weeks lab, make sure:
- you understand how simple assembly instructions are executed by the discoboard (and can read the cheat sheet to figure out what they do)
- youre able to follow the execution of your program in the debugger, and inspect registers to see whats going on
- you have read the laboratory text below
In this weeks lab you will:
- translate simple mathematical expressions into sequences of assembly instructions
- watch the status register and monitor the condition flags
- branch (jump) around in your program, including conditional branches
Useful reference material:
And heres the conversion widget again, because it always comes in handy:
Decimal | |
Hex | |
Binary |
Introduction
Pokemon is a game where you play as a pokemon trainer and assemble a team of fictional creatures, for battleagainst wild pokemon (to capture), and friends/foes (to prove your superiority). In this weeks lab, youre going to create your very own gamea (much) simplified version of Pokemon.
In this game, you will have a pokemon character take turns with an enemy pokemon in an epic battle until one of the two pokemon runs out of hit points (HP) and faints! This isnt a game for the faint of heart You will start by creating your own pokemon and then we will add in the enemy pokemon later.
Its important to ask for help when you get stuck in your lab. If youre an on-campus student: wave to one of your neighbours. If youre online: say hey in your Teams chat! Ask what their favourite card game is, and whether theyve ever thought of writing a computer-program version of it?
Fork & clone the lab 3 template to your machine and lets get started.
Exercise 1: Creating your pokemon
In the lab 3 template you will find the following code already provided to get you started.
.syntax unified.global main.type main, %functionmain: @ b exercise_3 @ Your code goes here@ a simple infinite loopend_loop: nop b end_loop.size main, .-main@ a place for your to complete exercise 3. uncomment line 6 when you're ready to start working on it!exercise_3:...
But before you write any more code, think: what are the minimum features that youll need to put in your program to create a game like this?
At the very least, the game needs:
- a pokemon, with some number of hit points
- an action that our pokemon can perform
This stuff is so common in computer games that you probably dont even think of it. Using what youve learned so far about programming your discoboard, how might you keep track of your pokemons hit points (HP), and how could you represent an action in your program?
Given what youve covered so far, you probably decided to use a register to store the pokemons HPgood choice. Remember in last weeks lab you used registers and instructions to perform your 2
+2
operation? This is the same idea. From the computers perspective the register is just a bunch of bits (well, a bunch of logic gates which store a bunch of bits) but as long as you know that that particular register really represents your pokemons HP and treat it accordingly, then your game will play just fine.
Write a program to store the pokemons HP in the register r0
with an initial value of 100
. Commit your main.S file and push it up to GitLab.
Youre also hopefully by now starting to get the hang of the way things can go wrong in assembly programming. For example, if you try to write an instruction which branches to itself:
my_label: b my_label
then your board will stop talking to you (thats why we put a nop
in the middle). And if you set a breakpoint on a label (like my_label
above) then it may end up breaking one instruction later than youd expect (this is a bug in the debugger). All of this stuff is a bit painful at first, but you will get the hang of it.
If all else fails, un-plug and re-plug your board (but once you get used to the pitfalls you shouldnt have to do that too often).
Exercise 2: Healing your pokemon
Now that youre storing the HP, heres the first action in the game:
- healing potion: add 50HP (hp := hp + 50)
Write an assembly instruction for this action. Remember to look at the cheat sheetnobody expects you to recall this stuff perfectly from memory.
Step through your programwhats the value in the hit point register after your action instruction has occurred? Does that seem right?
At this point, the Pokemon world only has one action, so the only way for the game to proceed is to keep performing that action. We can do that with a branch instruction: b
(hint: check page 2 of your cheat sheet). This instruction tells your discoboard to branch (sometimes called a jump on other CPU types) to a different part of the code. You can specify the destination of the branch in a bunch of different ways, including using a label, or a constant value (if you know exactly what address you want to go to ahead of time) or even the address in a register. If youve wondered how to get your program to do something other than just keep following the instructions from top to bottom, branching is the answer.
Add a label and a branch instruction to modify your program so that the pokemon keeps drinking healing potions (one after the other) indefinitely.
Hit the continue (play) button in the debug toolbar and let the program run for a while, pausing every now and again to check the pokemons HP valuewhat do you notice?
What do you think is going on here (think about this: whats the biggest number a register can store?)
Exercise 3: Status Flags and Condition Codes
How can you deal with this problem? The answer lies is in the program status register in every ARMv7 CPU (including our little discoboard). You can see it in the cortex registers viewlet in VSCode under xPSR
:
Remember we talked about these status flags in the week 2 lectures (go and have a look if you need to refresh your memory). This stackoverflow post also has a nice clock animation to show how all the condition codes in the status register work.
When the discoboard executes any instruction with an s
suffix (e.g. adds
) it updates these status flags according to the result of the operation. Thats all the s
doesadd
and adds
will leave the exact same result in the destination register, but adds
will update the flags to leave some breadcrumbs about the result (which can be helpful, as youll soon see).
In addition to this, if you look at the Tests section of the cheat sheet then you can see that there are some instructions specifically used to update the flags without changing the values in the general purpose registers (r0
r12
). For example, cmp r0, 10
is the same as subs r0, 10
except that the value in r0 is left untouched.
Sometimes the status flags are called status bits, or condition flags, or condition codes, or some other combination of those words. They all refer to the same thingthe bits in the program status register.
Its time to see this in action. Go back to your healing loop program from Exercise 1 and step through, but this time keep an eye on the xPSR
register. As a tip, you probably want to bump up the healing amount to something like 0xF000000
so that it doesnt take you a million steps before you overflow 🙂 What do you notice about the status register bits when the HP register switches to a negative (when viewed as a signed decimal number) value?
Write a series of simple programs (e.g. mov
some values into registers, then do an arithmetic operation on those registers) to set (a) the negative flag bit (b) the zero flag bit (c) the carry flag bit and (d) the overflow flag bit.
Uncomment the b exercise_3 instruction on line 7 of your project, and use the template underneath the exercise_3 label to fill out your solutions. When youre done, you can re-comment b exercise_3 and use it as a reference.
exercise_3:@ set the negative flag@ ... your instruction(s) go here ...@ set the zero flag@ ... your instruction(s) go here ...@ set the carry flag@ ... your instruction(s) go here ...@ set the overflow flag@ ... your instruction(s) go here ...
If youre getting bored of stepping through every instruction, dont forget you can set breakpoints, these control exactly where your debugger will pause after clicking continue (the green button). You can do this by clicking in the left-hand gutter (or margin) of the code view. You should see a little red dot appear:
Push up your program for Exercise 3.
The program status register xPSR
is a bit different from the other registers, and you cant use it in all instructions. You can access it with special instructions, thoughcheck out MRS
and MSR
in the ARMv7 reference manual. Can you think of a way (or an addition to your program) to make your life easier in reading the program status register?
It might seem like this carry/overflow stuff isnt worth worrying about because itll never happen in real life. But thats not true. It can cause serious problems, like literally causing rockets to explode. So understanding and checking the status flags really matters 🙂
Exercise 4: Enriching our game world
At the moment our game is pretty boring, we have a pokemon that constantly increases its HP by drinking a healing potion. Lets make the game more exciting by adding an enemy pokemon, that we can ruthlessly damage in each turn. This enemy pokemon will also need its own HP so that we can add a loss condition. This means that when the enemy pokemon eventually faints, we can tell (and win!)
Your pokemon will just have one attack for now, which subtracts a certain amount of health from our enemy pokemon.
Youve seen in lectures (and every time you look at the cheat sheet) that most of the ARM assembly instructions can be made to execute conditionally using a one or two letter suffix. (e.g. eq
, mi
, vs
)
What do these instruction suffixes do? What about the instruction bne end_loop
. You can look at your cheat sheet if you like.
Go back up to the top of your program (just under the main
label) and add the following instructions. Keep the rest of your pokemon code further down, just make sure this stuff is at the top.
movs r0, #100beq end_loop @ conditional branch 1subs r0, #200beq end_loop @ conditional branch 2adds r0, #100beq end_loop @ conditional branch 3
Which of the beq
instructions do you think will actually trigger the branch to the end_loop
?
Then, step through and see what happens. Can you change the condition (i.e. change the eq
to something else) to execute conditional branch 2? How about branch 1? Have a look at the Condition codes section of the cheat sheet to familiarise yourself with all the options.
Delete the above lines from your program and discuss how you can now use this conditional branching behaviour to make Pokemon work like a proper game:
- at the end of your pokemons turn, check if the enemy pokemon has faintedif so, branch to a
pokemon_wins
loop at the end (youll have to add this in, too) - otherwise if the enemy pokemon hasnt fainted, branch back up to the start of your pokemons turn and start again.
Push up your program for Exercise 4 with two pokemon, each with their own HP and alternating turns. If the enemy pokemons health is 0 or less, branch to an infinite loop labelled pokemon_wins
.
Exercise 5: the empire fights back
Weve been a bit unfair to our enemy pokemon at this pointit has to just sit there and take damage! Thats not a very fun game, so lets give our enemy a turn attacking! Give your enemy pokemon its own turn and let it attack your pokemon back! Pick an attack that means that your pokemon faints just before the enemy pokemon would. This means we need another loss condition for our own pokemon, i.e. at the end of the enemy pokemons turnif your pokemon has fainted, branch to an enemy_wins
loop. If both of the pokemon are still awake at the end of both pokemons turns, branch back up to the start of your pokemons turn and start again.
Run through your code until your pokemon faints. Do all of the steps behave as expected?
Commit and push your enemy pokemon.
Exercise 6: conditionals and branching
Were now going to do two things to make our game more interesting:
- Add in a strength variable for each of our pokemon (youll need to use two new registers for this, one for each pokemon). This strength variable will change the power of some of the attacks. Choose an initial value for the strength which makes sense to you (perhaps try 10 and see what happens)
- Add more attacks that do more than just decrease the health of our enemy
Here are some attacks weve come up with:
- fireball: remove 20HP
- frenzy: remove 30HP, but add 10 strength to the opposing pokemon
- magic poison: do double the pokemons strength attribute in damage (hp := hp 2 * strength)
- strength from weariness: hp := hp + ((hp-strength)/20)^2
- eye of Zardok: strength := (hp * strength) / 100; HP := HP (strength / 16)^3
Note that these ones (unlike the simpler actions from the previous section) might require more than one instruction, so feel free to use whitespace and comments (any text on a line following the @
character is a comment) to make things clearer. You can use whatever registers you like, but make sure that the destination register of each instruction isnt already holding something important (like your HP!). For more complex mathematical expressions, you need to break it down into smaller steps. Use a pencil and paper if that helps.
Choose one of the first three, and one of the last two to implement yourself.
Come up with an idea for your own action, share it with a neighbour (or send it to the rest of your lab on Teams).
Implement your own custom action (or someone elses) and add it to either your pokemon or the enemy pokemon.
Reviews
There are no reviews yet.