In the early days of the microcomputer industry, you had to laboriously enter machine code directly using toggle switches. A classic example is the Altair 8800 (shown below though possibly a replica):

With this, you would set the binary switches for the address and/or data bits, then use one of the command toggle switches to either:
- set the current address to whatever is indicated by the address switches; or
- write the data from the data switches to the current/next address.
The Altair, after RESET
, had the current address set to zero and, since this is where the CPU will start executing code, that was usually sufficient. Otherwise, you could set the address switches and toggle EXAMINE
to set the current address to something else. That address selection process was also needed in the case where you had entered your program incorrectly and needed to patch it (rather than doing it all again from scratch).
Once the address was what you wanted (EXAMINE
would show it on the address LEDs along with the current content on the data LEDs), you would toggle the data switches and do DEPOSIT
to put that data into the current address (DEPOSIT-NEXT
was identical but it first incremented the current address before depositing, useful for sequential entry).
The whole process can be found on sites like this one, with the basic idea being:
- Write the program out by hand, in assembly language.
- Hand-assemble that into binary code, using whatever manuals you have for the CPU, giving you a series of address/data pairs.
- Use the
EXAMINE/DEPOSIT/DEPOSIT-NEXT
process to enter each byte into memory, being very careful that the LEDs correctly indicate the byte you just deposited.
That second bullet point is probably the hardest bit since you need to adjust for jumping to instructions that may not have yet been assembled, so you're basically basically a slow, biological, multi-pass assembler.
For an example, consider the following (very simple) 8080 code, gleaned from online sources:
0000 c3 00 00 jmp 0000 ; Infinite loop.
Note those data bytes c3 00 00
are hexadecimal but people often used octal since the Altair switches were grouped into three rather than four; that would give 303 000 000
.
Hence, you would, for that specific program:
- Toggle
RESET
, giving a current address of zero.
- Enter binary data switches
101 000 101
for octal 303
, the JMP
opcode.
- Toggle the
DEPOSIT
switch, putting that byte into memory location 0, and showing that address and the deposited data on the LEDs for checking.
- Enter binary data switches
000 000 000
for octal 000
, the first byte of the address to jump to.
- Toggle the
DEPOSIT-NEXT
switch, advancing the current address to 1, depositing the byte in that address and again showing the address and data on the LEDs.
- Toggle the
DEPOSIT-NEXT
switch again, advancing the current address to 2, depositing the byte in that address and again showing the address and data on the LEDs. There was no need to change the data switches as they were identical to the previous byte.
At that point, the program is in memory, and you can toggle RESET
then RUN
to see it in action. Though, if you wanted to see the LEDs change as it ran, you'd probably continuously toggle SINGLE-STEP
instead.
The other thing to keep in mind is that there's no requirement that a program be written on the platform it's intended to run on. Cross-assemblers (or compilers) running on another platform can be used to bypass the laborious hand-assembling process described above.
You still have to get the bytes into the target platform but there were solutions for that as well. Paper tape readers could be added to Altair so, provided the cross-assembler system could produce the tapes, only a small loader program would be needed to load and run it.
For example, in the early embedded days (before "embedded" meant huge honkin' Linux boxes), I was part of a team developing operating system, drivers, and application code for a 6809-based device. The actual development tools were all hosted on an early UNIX system, and these were burnt onto EPROMS for insertion into the devices. No development at all took place on those devices, as they were very tailored toward their one real task.