Tiny Binary Computer Programming Patterns

As of Summer, 2026, this page 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 copy 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

Shortcut I/O for Numbers and Strings

The IN and OUTx instructions provide shortcuts for output. Using the shortcuts will let you focus on other parts of assembly programming without dealing with the details of input and output. To learn one way that real computers handle input and output, see the PIO instruction.

The IN instruction reads a number and places it in the accumulator. Numbers are limited to −2048 to +2047. Any other input causes a machine check.

The OUT instruction displays the contents of the accumulator as an integer followed by a new line. The OUTN instruction is identical but does not follow the number with a new line. The OUTC instruction displays the contents of the accumulator as a character. The accumulator must be between 0 and 0xFF. The character displayed is the corresponding character from the first 256 code points of Unicode. To move to a new line, display 0x0a.

The OUTS instruction displays all characters of a null-terminated string. In the case of OUTC, the accumulator must contain the address of the string. The string ends when a 0, or null, is encountered. If a new line is wanted, include 0x0a before the terminating null.

  1. ldi hello // get address of string
  2. outs // output string
  3. hlt // Done!
  4. hello dat "Hello World from OUTS",0x0a,0
copy

Use PIO to Display a Character

  1. // Output using programmed I/O.
  2. // This program displays "A" on the screen, then halts.
  3. // For more than one character, see "Loop through a string"
  4. ldi 0 // Output is device 0
  5. sta ioDev // Store 0 in device register
  6. lda A // Get data for output
  7. sta ioData // Store it in data register
  8. pio // Start the I/O operation
  9. loop lda ioStat // Read I/O status
  10. sub one // Check for 1
  11. brz done // If one, I/O is complete
  12. br loop // Otherwise, keep checking
  13. done hlt // Halt
  14. ioData equ 0xff // EQU lets us give a name to a value
  15. ioDev equ 0xfd
  16. ioStat equ 0xfe
  17. A dat "A" // Data to display
  18. one dat 1 // Constant 1
copy

Use PIO to Read Keystrokes

  1. // Input using programmed I/O.
  2. // This program displays reads characters and stores them
  3. // in memory until "enter" is pressed. The "enter" is not stored.
  4. // A null (0x0) is added at the end of the buffer.
  5. // This also shows how to use "store indirect accumulator."
  6. ldi buffer // Gets ADDRESS of "buffer"
  7. sta ptr // Store buffer address in PTR
  8. ldi 1 // Input is device 1
  9. sta ioDev // Store 1 in device register
  10. in pio // Start the I/O operation
  11. loop lda ioStat // Read I/O status
  12. sub one // Check for 1
  13. brz store // If one, I/O is complete
  14. br loop // Otherwise, keep checking
  15. store lda ioReg // get character
  16. sub enter // was it enter key?
  17. brz finish
  18. lda ioReg // Not enter, get character back
  19. sia ptr // Store in buffer pointed by ptr
  20. lda ptr // get the pointer
  21. inc // point to next location
  22. sta ptr
  23. br in // Get next
  24. finish ldi 0 // Null for end of string
  25. sia ptr // Add to end of buffer
  26. hlt // Halt
  27. ioReg equ 0xff // EQU lets us give a name to a hex address
  28. ioDev equ 0xfd
  29. ioStat equ 0xfe
  30. enter dat 0x0a // Code for enter key
  31. one dat 1
  32. ptr dat // pointer for store indirect
  33. buffer dat // data will go here
  34. // Anything below here could get overwritten by the input!
copy

Using PIO to Read Numbers

  1. ldi 0
  2. sta number // initialize number
  3. ldi 1 // Input is device 1
  4. sta ioDev // Store 1 in device register
  5. in pio // Start the I/O operation
  6. loop lda ioStat // Read I/O status
  7. sub one // Check for 1
  8. brz save // If one, I/O is complete
  9. br loop // Otherwise, keep checking
  10. save lda ioReg // get character
  11. sub enter // was it enter key?
  12. brz finish // Yes. Done
  13. lda number
  14. push
  15. call mult10 // shift result by multiplying by 10
  16. pop
  17. sta number
  18. lda ioReg // Not enter, get character back
  19. sub offset // convert symbol to number
  20. // error checking here
  21. add number // add current digit to number
  22. sta number // and save the sum
  23. br in // Get next
  24. finish ldi msg
  25. outs
  26. lda number
  27. out
  28. hlt // Halt
  29.  
  30. ioReg equ 0xff // EQU lets us give a name to a hex address
  31. ioDev equ 0xfd
  32. ioStat equ 0xfe
  33. enter dat 0x0a // Code for enter key
  34. offset dat 0x30
  35. one dat 1 // Constant 1
  36. number dat
  37. msg dat "The number entered was ",0
copy

Comparison Operations with TBC

Programming in a high-level language like Python, it is common to write something like if (a > b) followed by a block of code to be executed if the expression in parentheses evaluates to true. The assembly/machine languages for many computer families have comparison instructions that more or less directly correspond to the high-level language statements. The very simplest machines, like TBC, do not. Instead, comparison is accomplished by subtracting the comparands and evaluating the result with a conditional branch.

In this section, we present three patterns, comparison for equality, greater than or equal, and strictly greater than. "Less than" comparisons are accomplished by reversing the order of the comparands. Note that a < b is the same as b > a.

Comparison for Equality

In the example below, all the work happens in lines 2 – 4. In line 2, the first comparand is loaded into the accumulator. In line 3, the second comparand is subtracted from the accumulator, and in line 4, a branch is taken if the result was zero, that is, if the two comparands were equal. The rest of the example is scaffolding to produce something that will assemble and run.

After the subtract instruction, the value of the accumulator is no longer equal to the variable a, however the stored values of a and b are unchanged.

  1. // if A = B
  2. lda a // First comparand
  3. sub b // subtract second comparand
  4. brz equal // branch if equal
  5. ldi neqmsg // report not equal
  6. outs
  7. hlt
  8. equal ldi eqmsg // report equal
  9. outs
  10. hlt
  11. a dat 60
  12. b dat 50
  13. neqmsg dat "Not equal",0x0a,0
  14. eqmsg dat "Equal",0x0a,0
copy

Greater than or Equal To

  1. // if A >= B
  2. lda a // Load first comparand
  3. sub b // Subtract second comparand
  4. bnn gteq // Branch if A >= B
  5. ldi bmsg
  6. outs
  7. hlt
  8. gteq ldi amsg
  9. outs
  10. hlt
  11. a dat 7
  12. b dat 6
  13. amsg dat "A >= B",0x0a,0
  14. bmsg dat "A < B",0x0a,0
copy

Strictly Greater Than

  1. // if A > B
  2. lda a // Load first comparand
  3. sub b // Subtract second comparand
  4. brz not // Equal, so not strictly >
  5. bnn gteq // Branch if A >= B
  6. not ldi bmsg
  7. outs
  8. hlt
  9. gteq ldi amsg
  10. outs
  11. hlt
  12. a dat 9
  13. b dat 8
  14. amsg dat "A > B",0x0a,0
  15. bmsg dat "A !> B",0x0a,0
copy

Loop Through a String

In this example we process a string, loading each character of the string into the accumulator and sending it to the screen with the outc command. The same looping technique could be applied to an array of numbers.

Suppose you have a string like this:

      msg      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:

  1. ldi msg // get address of msg
  2. sta ptr // Save address in "ptr"
  3. loop lia ptr // value loaded from address in "ptr"
  4. brz done // null means done
  5. outc // Output character
  6. lda ptr // pointer back in Acc
  7. inc // Increment to next character
  8. sta ptr // Save it
  9. br loop // Go around
  10. done hlt
  11. ptr dat
  12. newline equ 0x0a // new line character
  13. msg dat "Hello, World!",newline,0
copy

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.

  1. ldi 1 // a number to display
  2. out // display it
  3. call two // subroutine call
  4. ldi 5
  5. out
  6. hlt
  7. two ldi 2
  8. out
  9. call three // a call within a call
  10. ldi 4 // "two" continues here
  11. out
  12. ret // returns to main program
  13. three ldi 3
  14. out
  15. ret // returns to "two"
copy

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.

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.

A Subroutine with Two or More Arguments

The design of TBC makes it impractical 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.

  1. ldi 5 // Some value for first argument
  2. sta a1
  3. ldi 4 // And a value for the second
  4. sta a2
  5. ldi a1 // Address of the "list"
  6. push // Param list address on stack
  7. call gfam // Multiply the two arguments
  8. pop
  9. sta result
  10. out // Display the result
  11. hlt
  12. a1 dat // the parameters must be together
  13. a2 dat
  14. result dat
copy

Note: to run this, you must also copy the gfam subroutine and add it to the source pane in the assembler.

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.

  1. gfam sswp temp // Go Forth And Multiply
  2. pop // address of parameter list
  3. sta params
  4. lia params // get first param
  5. sta mcand // store multiplicand
  6. sta product // product now multiplicand times 1
  7. lda params
  8. inc // increment to next param
  9. lia params // get second param
  10. sub one // product already times 1
  11. sta mplier // store multiplier
  12. loop lda product
  13. add mcand
  14. sta product
  15. lda mplier
  16. sub one
  17. sta mplier
  18. brz return
  19. br loop
  20. return lda product
  21. push
  22. sswp temp
  23. ret
  24. temp dat 0,0
  25. params dat // address of caller's argument list
  26. mplier dat
  27. mcand dat
  28. product dat
  29. one dat 1
copy

Multiplication by Ten (Special Case)

Multiplication by ten happens frequently with decimal numbers, and

  1. mult10 sswp temp // Exchange param and return address on stack
  2. pop // Get number to be multiplied
  3. brz retzero // Special case: 0 x 10 = 0
  4. sta temp // Already times 1
  5. add temp // Now times 2
  6. add temp // times 3
  7. add temp // times 4
  8. add temp // times 5
  9. sta temp // Save the "times 5" value
  10. add temp // And add it back. Now times 10
  11. push // Save the return value
  12. sswp temp // Fix the stack
  13. ret // And return to caller
  14. retzero ldi 0
  15. push
  16. sswp temp
  17. ret // return zero
  18. temp dat
copy

Sorting

Sorting a list is a big job for an imaginary computer with only 128 words of memory, but it can be done using the bubble sort algorithm. Bubble sort has time complexity of O(n2) which is poor, but it needs only one extra word of storage and the algorithm itself is compact. The example below uses 125 of the 128 words. There's a main program with a list of items to sort, the sort algorithm itself, and a small print subroutine to list the data before and after sorting. The actual sorting begins at line 15 and ends with the ret instruction at line 69.

Lines 18 to 27 just loop through the list, counting the number of items. Lines 28 to 30 set up index variables and limits. The actual sorting happens between lines 31 and 68.

  1. // Bubble sort
  2. ldi data // Get the ADDRESS of the list to be sorted
  3. push // Put address on stack
  4. call print // print unsorted data
  5. ldi data // Get address again
  6. push // On stack
  7. call sort // Call the sort subroutine
  8. ldi data // Address of data, not sorted
  9. push
  10. call print // Print it again
  11. hlt // and stop
  12. data dat 34, 90, 12, 11, 25, 64, 22, 0 // 7 items plus null
  13. // The null above signals end of list; it isn't part of the data
  14. sort sswp temp
  15. pop // address of data
  16. sta start // save it
  17. sta ptr // save it again
  18. getlen lia ptr // Loop through the data, count the items
  19. brz gotlen // if this was the last one
  20. lda ptr
  21. inc
  22. sta ptr
  23. ldi 1
  24. add len
  25. sta len
  26. br getlen
  27. gotlen lda len // we now have the length of the list to sort
  28. sub one
  29. sta ilimit // Limit of outer loop
  30. outer ldi pass // Display a progress message
  31. outs
  32. lda i
  33. out // Pass number in progress message
  34. inc
  35. sta i
  36. sub ilimit // Have we reached limit of outer loop?
  37. brz endouter
  38. // This initializes the inner loop
  39. lda len
  40. sub i // I has been incremented already
  41. sta jlimit
  42. ldi 0
  43. sta j
  44. inner lda j
  45. // This is the actual compare and exchange
  46. lda start // start of list
  47. add j
  48. sta a1 // address of item at J
  49. add one
  50. sta a2
  51. lia a1 // get actual data
  52. sta temp
  53. lia a2
  54. sub temp // compare
  55. bnn continu // If they do not need to be exchanged
  56. lia a2 // Otherwise, exchange the two items
  57. sia a1
  58. lda temp
  59. sia a2
  60. continu lda j
  61. inc
  62. sta j
  63. sub jlimit // Have we reached the inner loop limit?
  64. brz endinner
  65. ldi j
  66. br inner
  67. endinner br outer // at end of inner loop go back to outer
  68. endouter ret // The list is now sorted!
  69. a1 dat
  70. a2 dat
  71. i dat 0 // index for outer loop
  72. ilimit dat // Ending value for outer loop
  73. j dat 0 // index for inner loop
  74. jlimit dat // ending value for inner loop
  75. len dat 0
  76. one dat 1
  77. pass dat "Pass ",0
  78. ptr dat
  79. start dat
  80. temp dat 0,0
  81. print sswp temp // Routine to print list of items before/after
  82. pop // Address of string
  83. sta nbr // load acc with actual number
  84. ploop lia nbr
  85. brz pdone
  86. outn // out with No new line
  87. ldi psep // print the separator
  88. outs
  89. lda nbr
  90. inc
  91. sta nbr
  92. br ploop
  93. pdone ldi 0x0a
  94. outc
  95. ret
  96. psep dat " ",0
  97. nbr dat
copy