Z80Friday, 13th March 2020 ◆ Crisp fragment (4)
Though I may be a programmer by profession, the inner workings of a computer largely remain a mystery to me. Modern computers would be far too complex for me to even know where to begin in terms of understanding how they work.
However, my dad recently got me a computer kit from CPUVille after I showed interest in his. It is comprised of minimal components with a Z80 microprocessor at its core. The Z80 was a popular chip in its day, being used for such things as home computers, arcade machines, and even the first generation of gameboy!
Using this kit, I think understanding a little more about the intricacies of computing is within my grasp.
As you can see from the picture, there aren't a whole lot of components. The Z80 chip is the largest one in the top-left, marked with Z.
It's an 8-bit processor which means that memory is loaded into its various registers 8 bits at a time. The Z80 has 28 registers, each holding 8 bits. It can move data between the registers, and perform operations on them.
One of those registers is called PC (program counter) and controls which bit of data the Z80 will process. At each iteration, the processor reads the value of the memory at the location specified by PC, increments PC, then executes the instruction it found.
The instruction set for the Z80 includes operations such as addition, subtraction, bitwise operations (AND, OR, etc.) and copying data between registers. You can see a table of instructions here.
In order to get started with the computer, I wrote fizzbuzz in it. However, that ended up being quite a lot of code. So intead of talking through the whole lot, let me share one part of the program with you - the mod function, written in the Z80's assembly language.
; Calculate a mod b and store the result in a
jp p, mod_b_loop ; If a still positive, subtract again
add b ; Once we are negative, add b again to get positive
Note that indentation means nothing to the compiler. I added it solely to help understand the program flow.
mod_b: is a label. It doesn't compile into any instruction itself, but if
mod_b is used elsewhere in the code, the compiler will replace it with the memory address of this label.
There is an instruction
call which saves the current value of PC in a stack, and sets PC to the argument given to it. The complement to this is
ret, an instruction which pops that stack, and sets PC to the value there. Effectively,
call jumps to a new memory location, and
ret returns you to the memory location you were at before. In other words, a function.
Another label – we will refer back to this one shortly. There is no
ret associated with this label because I don't intend to use
call to jump to it - instead I will be using
jp or "jump". This is similar to
call in that it sets PC to the value given, but it does not store the previous value of PC, and so there is nothing to return to. This is equivalent to a "goto" in C.
This instruction subtracts register b from register a. You can see that I don't specify a anywhere - that's because register a (also called the accumulator) is used for most of the mathematical operations. The result is stored back in a. When mathematical operations like this are done, the flags register (or f) is also modified to provide additional information. The f register is 8-bits (of course), so there are 8 flags which can be either 1 or 0. The flag we are interested in is the S flag, or the sign flag. If gets set to 1 if the result of the operation is negative, and to 0 if it is not (that is, if it is positive or zero).
jp p, mod_b_loop
This form of
jp has a condition.
jp p means set PC to the given location (or, jump to the given location) if the S flag is not set. In other words: jump if not negative. (The condition is called p for positive, but it is worth noting that really it means non-negative, since 0 would also cause the jump.) That is, if the accumulator still holds a positive number or zero, then jump back to the beginning of the loop for another subtraction. If the S flag is set, then PC is not modified, and no jump occurs.
You can see here I am using the label we defined:
mod_b_loop. The compiler will swap this out for the address of that label. You can write the address manually too, of course, but that quickly gets awkward.
Once we get a negative number, we will quit the loop by skipping the jump command. At this point we add b back to a so that our result is a number in \([0, a)\).
And that's it! The a register now contains the result of a mod b.
If you fancy playing around with Z80's asm, there is an online emulator.