The assembler - introduction

As already mentioned in the previous sections about programming, an assembler is a utility to translate assembly-language into machine-code.

This section shows how to use the assembler in more detail.

By now, the reader is assumed to be familiar with the concepts of assembly-language, macros, and the semantics of some basic (pseudo-)instructions. (Refer back to previous sections if necessary.)

The assembler is implemented in the Tcl programming language, which is excellent for implementing Domain-Specific Languages (DSL) such as the assembly-language used in this project.


Labels are placeholders for program-addresses.

Labels already appeared in the previous sections. An example of a label-definition and -reference is shown below:

        ibc1    1000    done    ;# Invert the bit at address 1000. 
                                ;# If the inverted bit is 0, 
                                ;# jump straight to "done",
                                ;# skipping the next instruction.

        set1    2000            ;# If the inverted bit was 1,
                                ;# set the bit at 2000 to 1.

: done 

The label "done" can be referenced by branch-like instructions occurring before or after the label-definition. (In the previous example, the label-reference occurred before the label-definition).

It's not allowed to reference a label defined outside the current macro.

Note the required whitespace between the colon (":") and the label-name ("done").


Variables are symbolic names for RAM-addresses. They already occurred in the section about extended assembly-language.

Each variable must be declared inside a macro-body, before its first use. Variables can be used as (part of) a data-address in an instruction.

An example of the use of variables is shown here:

macro MyUselessMacro {

    . UselessVar

    set1    $UselessVar     ;# Set variable "UselessVar" to 1. 
                            ;# Since the variable is lost when
                            ;# the macro-body ends, this is
                            ;# a useless operation.

Note the required whitespace between the dot (".") and the label-name ("UselessVar").

When a variable is declared, the next free RAM-address is allocated to it. When a macro-body ends, the RAM-addresses corresponding to all its local variables are freed.


The assembly-language used here is in fact Tcl-code. To assemble a program simply means to execute it as Tcl-code. During execution, the resulting program-binary is written.

Because assembly-language is Tcl-code, assembly-statements can be mixed with Tcl-expressions and -statements. Additional helper-variables, loops and conditionals can thus be used:

include "1bit.asm"      ;# The assembler offers "include",
                        ;# which literally inserts another file.
                        ;# The file "1bit.asm" contains macros
                        ;# like "clr1", used in the code below.

main  {

    # This code clears 16 bits starting at RAM-address 8000.
    # (Only "clr1" is an actual assembly-instruction; the rest
    # are generic Tcl-statements.)

    set base 8000

    for  { set offset 0 }  { $offset < 16 }  { incr offset } {

        clr1  [ + $base $offset ]   ;# "+ A B": Polish notation

(For an explanation of what each Tcl-statement does exactly, consult a Tcl language-reference.)

In the above example, a Tcl-variable "base" was created. This variable can be referenced using Tcl's "$"-notation, as shown above.

In the example before this one, another variable named "UselessVar" was declared using the assembler's dot-notation (". UselessVar"). By doing this, a Tcl-variable with the same name came into existence. This variable could also be referenced using the "$"-notation.

Every expression that's allowed in Tcl is allowed in the assembly-language, since that is also Tcl. Furthermore, most binary numeric operators are made available in Polish notation, e.g. ">= 3 2", or "+ $base $offset" as shown above.

However, expressions can not be used with labels.

(This has to do with the fact that it's impossible to know the label-address if it hasn't been defined yet at the time of use. Remember that labels are not simply Tcl-variables.)


A macro can have arguments (positional parameters), if so specified in the macro-definition.

The previous example-macro "MyUselessMacro" had no arguments. The following macro has 2 arguments - "FirstAddr" and "SecondAddr":

macro  SomeOtherMacro  FirstAddr  SecondAddr {


These arguments "FirstAddr" and "SecondAddr" in the macro-definition are called the macro's formal arguments.

When the macro is inserted, actual values - called actual arguments - must be given in place of these symbolic argument-names:


SomeOtherMacro 1000 2000


Within the macro-body, the given actual arguments will then be substituted for the formal arguments:

macro  SomeOtherMacro  FirstAddr  SecondAddr {

    clr1    $FirstAddr    ;# Clear the bit at address 1000...

    set1    $SecondAddr   ;# ...and set the bit at 2000...

SomeOtherMacro 1000 2000  ;# ...because those values were passed.

Actual arguments can be literals, variables, expressions containing either or both, or label-names. Expressions using labels are not allowed. (This has to do with the fact that labels are not actual Tcl-variables.)

A somewhat more complex example, using variables to pass a label-name and RAM-address around:

macro  ClearAndBranch  reg  branch {

    ibc1    $reg    $branch
    ibc1    $reg    $branch

macro  UnconditionalBranch  branch {

    . tmp   ;# (dummy-variable)

    ClearAndBranch  $tmp    $branch


    UnconditionalBranch done

    set1 1000   ;# This instruction is always skipped.

: done


(Since macro-definitions must precede their use, it may help to read this example from bottom to top.)