Software is remarkable stuff. Ever since writing my first computer program in October 1974* I have not lost that odd but exhilarating sense that writing a program is like working with pure mind stuff. Even now, over 32 years later, when I fire up Kylix** on my laptop and crack my fingers ready to starting coding I still feel the excitement - the sense of engineering something out of nothing in that virtual mind-space inside the computer.
But there is an even more remarkable place that I want to talk about here, and that is the place where hardware and software meet. That place is called microcode.
Let me first describe what microcode is.
Most serious computer programming is (quite sensibly) done with high-level languages (C++, Java, etc), but those languages don't run directly on the computer. They have to be translated into machine-code, the binary 0s and 1s that actually run on the processor itself. (The symbolic version of machine code is called 'assembler' and hard-core programmers who want extreme performance out of their computers program in assembler.) The translation from the high-level language into machine-code is done by a program called a compiler and if, like me, you work within a Linux environment then your compiler will most likely be the highly respected GCC (Gnu C Compiler).
However, there is an even lower level form of code than machine-code, and that is microcode.
Even though a machine-code instruction is a pretty low-level thing, like 'load the number 10 into the A register', which would be written in symbolic assembler as LD A,10, and in machine-code as an unreadable binary number, it still can't be excuted directly on the processor. To explain why I first need to give a short tutorial on what's going on inside the processor. Basically a microprocessor is a bit like a city where all of the specialist buildings (bank, garage, warehouse, etc) are connected together by the city streets. In a microprocessor the buildings are pieces of hardware that each do some particular job. One is a set of registers which provide low level working storage, another is the arithmetic logic unit (or ALU) that will perform simple arithmetic (add, subtract, AND, OR etc), yet another is an input-output port for transferring data to the outside world. In the microprocessor the city streets are called data busses. And, like a real city, data has to be moved around between say the ALU and the registers, by being routed. Also like a real city data on the busses can collide, so the microprocessor designer has to carefully avoid this otherwise data will be corrupted.
Ok, now I can get back to the microcode. Basically, each assembler instruction like LD A,10 has to be converted into a set of electrical signals (literally signals on individual wires) that will both route the data around the data busses, in the right sequence, and select which functions are to be performed by the ALU, ports, etc. These electrical signals are called microorders. Because the data takes time to get around on the data busses the sequence of microorders has to carefully take account of the time delays (which are called propagation delays) for data to get between any two places in the microprocessor. Thus, each assembler instruction has a little program of its own, a sequence of microorders (which may well have loops and branches, just like ordinary high level programs), and programming in microcode is exquisitely challenging.
Microcode really is the place where hardware and software meet.
*in Algol 60, on a deck of punched cards, to run on an ICL 1904 mainframe.
**which I am very sorry to see has now been discontinued by Borland.