lc3db is a functional simulator/debugger for the LC-3
architecture. It provides a GUI using the GNU
This is the First Edition of Using the LC-3 Debugger, 2004-04-19, for
lc3db Version 0.3.0.
lc3db's builtin assembler
lc3dbstartup code including a description of the boot process.
The goals of
lc3db are to create a robust and complete
simulator package for the LC-3 architecture. It differs from other LC-3
simulators in a few ways. It is structured more like a debugger than a
simulator. This was done to leverage existing GUI interfaces. The current
GUI mode is more or less as functional as the existing simulator GUI (and has
a few additional features). However, like all new code, there could be some
bugs. Feel free to report any behavior you find to be odd or things that you think are just plain bugs to Anthony Liguori.
The first thing you need to do is run the simulator. You will need to either be on a UT CS machine or using some sort of remote client capable of displaying X. Basically, the same requirements for the other LC-3 simulator.
You can run the simulator with the following command:
This should bring up a Window that looks as shown below. If a window pops with the query
aliguori@bubble:~$ /p/bin/lc3db --ddd
The very top portion of the screen is the data window. The GUI allows you to plot data graphs. This is an advanced feature that we will not cover in full except to say that this plot contains all of the machine state. It will automatically be updated every cycle and the data that changed will be highlighted.
The middle portion of the screen contains a disassembled version of the memory for about a thousand bytes after the PC. Some important things to note about this code:
The command window is the very bottom portion of the screen. All aspects
lc3db can be accessed via this command interface. Before we
discuss some of the important commands, we'll cover the three buttons that
appear on the top of the window.
This command does what you'd expect it to. It boots the LittleOS operating system. If you are writing code that uses interrupts, it is very important that you do this before loading/running anything else. This command will also launch an additional IO console which is useful if you want the input/output for the simulator to be separate from the console.
The step command steps through a single instruction in the simulator. This means it will enter into JSR and TRAP instructions.
The next command will step through a single instruction in the simulator. It differs from the step command in that it will not enter into a JSR or TRAP instruction. This is very useful if you do not want to sift through all of the builtin TRAP routines.
You can see all of the builtin console commands by typing help into the console.
After you've compiled your program (see the section on Assembling for more
info on this), you can load your program by using the
File => Open
Program menu (or the console). Using the
File => Open
Program menu would look something like:
lc3db has a builtin in assembler. This section will discuss
some of the differences in syntax of the assembly code and how to use it to
compiler your programs. It is perfectly fine to use the assembler from the
previous simulator to compile your programs. However, if you wish for it to
show up in Open Program dialog, it must have execute privileges. Also, the
symbol table format is not compatible between assemblers so you will lose all
There is a special script installed on the UT CS network to make using the builtin compiler quite simple. Its usage is pretty straight forward. To compile filename.asm, use the following command:
This will produce two files: filename.obj and filename.dbg.
aliguori@bubble:~$ /p/bin/lc3asm filename.asm
LittleOS is a basic operating system for the LC-3. It supports all of the
standard traps as defined in P&P (except for PUTSP). The one thing that
makes LittleOS unique is that it defines a boot sequence (including a boot
interrupt). The following is the code for this boot sequence:
You'll notice this code simply loads R6 with the address of a supervisor stack
(with some default entries on it), adjusts R6, and executes an RTI. This
effectively boot straps the SSP register with a stack that will be used for the
rest of the machines execution. You'll notice the stack is initially populated
with a PC, PSR, and default USP. The reasons for this are discussed in the
LEA R6, SUPERVISOR_STACK
ADD R6, R6, -3
You can access the source code for LittleOS on the UT CS network under
lc3db is the only LC-3 simulator (that I know of at least)
that has a complete implementation of interrupts. However, the implementation
is slightly different than described in P&P mainly to support pre-emptive
multi-tasking. An interrupt is essentially a function call that is called by
the processor during normal execution. Just like a function call, a stack has
to be used to be stored things in a particular way for interrupts to work
How interrupts are triggered will not be covered here. Instead, assume that for whatever reason, the processor has decided to take an interrupt. As programmers, we're interested in what information we're given in the interrupt handler itself.
When the interrupt begins, the only register with a known value is R6. This register will hold the top of the supervisor stack. The top three elements on this stack will also be known. The top of the stack will contain the PC before an interrupt occurred. The next element of the stack will be the value of R6 before the interrupt occurred. The third element will be the PSR before the interrupt. The interrupt handler is free to modify these values and, indeed will be forced to in implementing a timer interrupt.
The timer interrupt relies on two special registers: the MCR and the MCC.
The MCR is the machine control register and is located at address xFFFE. The
values of MCR are as follows:
Additionally, the MCC, or machine cycle counter, is located at xFFFF. This value is incremented every time a cycle occurs. The timer interrupt is actually triggered when
MCR = clock enable MCR = timer interrupt enable MCR[13:0] = cycle interval between timer interrupts
MCC[15:0] >= ZEXT(MCR[13:0]).