Tiny Binary Computer Programming Patterns
As of Summer, 2026, this is a work in progress.
Programming in assembly language is different from programming in a high-level language like Python. Something as simple as displaying "Hello World!" on the screen can take over a dozen assembler statements and a hundred or so instruction times. This page explains how to make TBC's assembly language do many of those things that are built in to high-level languages.
Clicking the copy icon
in a code block will copy that code block to the clipboard. You can then paste it into the assembler source pane or into a program file.
Table of Contents
- Input and Output
- Subroutines
- Utilities
Display a String
Although TBC has a "behind the scenes" mechanism for displaying numbers, displaying a string involves loading each character of the string into the accumulator and sending it to the screen with the outc command.
Suppose you have a string like this:
hello dat "Hello,World!",0x0a,0
That 0x0a is the new line character. It will move the display to the next line. The 0 marks the end of the string. It assembles to a word of all zeros, also called a null. Here is code to display a string:
ldi msg // get address of msg
sta ptr // Save address in "ptr"
loop lia ptr // value loaded from address in "ptr"
brz done // null means done
outc // Output character
lda ptr // pointer back in Acc
inc // Increment to next character
sta ptr // Save it
br loop // Go around
done hlt
ptr dat
newline equ 0x0a // new line character
msg dat "Hello, World!",newline,0
- Line 1 is a Load Immediate instruction. The address of variable msg is placed in the instruction when the code is assembled, so a second memory reference is not needed at runtime. If we had used lda instead, that would have loaded the letter "H" into the accumulator.
- Line 2 stores that address in a pointer variable called ptr.
- Line 3 is Load Indirect Accumulator. "Indirect" means to get the operand address from someplace other than the instruction; in this case, the operand address is in the accumulator itself. So, that lia loads the accumulator using the address that was in the accumulator, namely the pointer into the string. After lia completes, the accumulator has a character from msg in it.
- Line 5 sends the character in the accumulator to the screen.
- Lines 6, 7, and 8 get the pointer back, increment it, and save it.
- The brz at line 4 says that if a zero value is loaded, the end of the string has been reached. That's why the message at line 14 ends with a zero.
- Line 13 defines newline as 0x0a, the code for a new line.
- The last line defines the message, followed by a newline character and a zero.
Simple Subroutine Calls
Subroutines provide a way to have only one copy of code that would be needed at two or more places. Instead of duplicating the code, the program calls the subroutine each time it is needed. When complete, the subroutine returns control to the instruction immediately after the call. The TBC instructions to do this are call and ret The program below just prints 1-2-3-4-5, but it uses subroutines to print the 2, 3 and 4 to show how subroutine calls work.
ldi 1 // a number to display
out // display it
call two // subroutine call
ldi 5
out
hlt
two ldi 2
out
call three // a call within a call
ldi 4 // "two" continues here
out
ret // returns to main program
three ldi 3
out
ret // returns to "two"
- Lines 1 and 2 use Load Immediate (ldi) and out to get a one into the accumulator and display it.
- Line 3 is the first subroutine call; it calls the subroutine called two. When two completes, it will return control the the main program at the next instruction, namely the Load Immediate on line 4.
- We skip ahead to line 8, the first line of subroutine two. It loads a 2 into the accumulator and displays it.
- Skip to line 15 and subprogram three. It gets and displays a 3, then executes a ret (return) instruction. Control will pass back to subroutine two at line 11, the line right after the call instruction.
- Subroutine two does more work, namely displaying a 4, then executes its own ret instruction, which returns to the main program at line 4, the line right after the call to two.
- The main program displays that last digit, 5, and halts.
Subroutines have the same problem Hansel and Gretel had; getting there is easy because the subroutine name comes right after the call. The problem is how to get back. The call and ret instructions use a stack that grows downward from address 0x07e to keep track of how to get back. A call puts the proper return address on the stack and a ret gets the address at the "top" of the stack and loads it into the program counter. Since the program counter holds the address of the next instruction, execution continues at that address.
Click the "copy" icon to copy this snippet, paste it into the assembler pane, assemble and run it. Watch what happens at the high memory addresses when the program runs.
Call a Subroutine with One Argument
If you have a subroutine that needs one argument that will fit in a 12-bit word, you can pass the argument on the stack.
Call a Subroutine with Two or More Arguments
It isn't practical to pass more than one argument to a subroutine using the stack. Instead, the calling program builds a parameter list and passed the address of that parameter list using the stack. Subroutines can pass the number of parameters as the first item of the list. Note: It is possible to return more than one result using the stack.
ldi 5 // Some value for first argument
sta a1
ldi 4 // And a value for the second
sta a2
ldi a1 // Address of the "list"
push // Param list on stack
call gfam // Multiply the two arguments
pop
sta result
out // Display the result
hlt
a1 dat // the parameters must be together
a2 dat
result dat
Note: to run this, you must also copy the gfam subroutine and add it to the source pane in the assembler.
- Lines 1 through 4 store two numbers at addresses a1 and a2. These are the two arguments.
- Line 5 loads the address of the argument list into the accumulator and line 6 pushes it onto the stack.
- Line 7 calls the subroutine.
- This subroutine returns only one result on the stack. Lines 8 and 9 pop the result and store it.
- The result is still in the accumulator. Line 10 displays it.
Multiplication
Like most early, small computers, TBC does not have a multiply instruction. Instead, multiplication is performed by repeated addition. That's somewhat slow, but it works. Here is the gfam (go forth and multiply) subroutine. Call it with an argument list of two words. See Call a Subroutine with Two or More Arguments for help with that. The product is returned on the stack. A pop instruction right after the call will get the product into the accumulator.
gfam sswp temp // Go Forth And Multiply
pop // address of parameter list
sta params
-
lia params // get first param
sta mcand // store multiplicand
sta product // product now multiplicand times 1
lda params
inc // increment to next param
-
lia params // get second param
dec // product already times 1
sta mplier // store multiplier
loop lda product
add mcand
sta product
lda mplier
dec
sta mplier
brz return
br loop
return lda product
push
sswp temp
ret
temp dat 0,0
params dat // address of caller's argument list
mplier dat
mcand dat
product dat
- The address of the parameter list was pushed before the call instruction, so the stack items are out of order. The sswp at line 1 exchanges them so that address of the parameter list is the first stack item.