The MSP 430’s instruction cycles and lengths can be found starting on
page 60 of the MSP430x2xx Family User’s Guide.
This description also includes the changes made to the status register by the
execution of each instruction.
A description of signal() and how to catch control C, can be found in
Introduction to signals.
For an example of signal() handling SIGINT (control C), click
A common question is, "where should execution begin?"
There are a number of possible choices:
Use an S9 record to specify the starting address. This requires the
assembler to recognize END
The user interface (debugger()) might be useful to have anyway, as it could be used to
determine the state of the machine.
For example, "R" could dump the CPU registers, "Mxxxx" could display memory from location XXXX
to, say, XXXX+32, and "X" could cause the program to stop.
When the CPU executes the HCF (Halt and Catch Fire) instruction, control could return to the
Note that this means changing the assembler to recognize the HCF instruction and creeating an
opcode for this instruction.
user_quit = FALSE;
debugger(); /* user_quit = TRUE if user indicates "quit" */
- if HCF opcode detected, hcf_detected = TRUE
A skeletal emulator for the TI 430 (to discussed in class)
can be found
For details on instruction cycles, see pages 60-61 and 145-147 of
the TI MSP 430 User's Guide.
There are two types of device that need to be emulated: input and output.
The problems are, how does the emulator indicate that an input
device has data for
it and that a device has written data to the output device?
To do this, two sets of data (possibly in one file) are needed.
First, it is necessary to indicate whether a device is used for input or
output (assuming that this information isn't hard-coded into the emulator).
If it is an output device, the time required by the device to generate the
output must also be specified.
This can be done in the first 16 records of the input file:
1 0 <- Record 0 - Dev 0 - Input (1) and processing time 0
0 100 <- Record 1 - Dev 1 - Output (0) and processing time 100
0 250 <- Record 2 - Dev 2 - Output (0) and processing time 250
1 0 <- Record 15 - Dev 15 - Input (1) and processing time 0
Second, it is necessary to emulate the arrival of data from an input devices.
This can be done in a series of records (in the same input file), with each
record indicating the time (in CPU "ticks"), the device number (0 to 15), and the
input data (such as a character). The records are sorted by time (i.e.,
time is increasing -- as it is in the CPU with sys_clock). For example,
10 3 A <-- Time 10, device 3 receives an 'A'
27 4 X <-- Time 27, device 3 receives an 'X'
85 3 b <-- Time 85, device 3 receives an 'b'
106 2 3 <-- Time 106, device 2 receives an '3'
The time between the arrival of data from a device is
dictated by the time value in the input file.
A rapid number of data arrivals from a device can be caused
by decreasing the time between each arrival.
This can be use to test for overflow.
A "device" is any virtual device that handles input or output (not both).
All devices (input or output) have both a status register and a
The status register indicates the state of the device, whether
A status change causes an interrupt or must be polled (IE)
It is an input or output device (I/O)
Data is available to be read (from the device, i.e., input) or
the device is ready for data (from the CPU, i.e., output) (DBA)
An overflow has occurred, typically occurring when the device supplies
data to the CPU at a rate faster than the CPU can process it (OF)
The data register:
Holds data from an input device.
The data is to be read by the CPU, clearing the data-available
indication, DBA (the contents of the register are unchanged after the
Holds data for an output device.
The data is written by the CPU, the device is then to output it after
the prescribed processing time (described above).
When the write starts, the device is no longer ready for data (DBA cleared),
on completion, the device can accept more data (DBA set).
All registers are paired and occupy one word of memory (low-byte for the
status register and high-byte for the data register).
Device registers occupy the first 16 words of memory (i.e., 0x0000 through
The input data from an input device is stored in a record in an input
file and consists of the TIME (time of input, 0 through infinity),
DEV (the device number, 0 through 15), and DATA (the input data — a
The emulator reads one record at a time.
When the CPU's clock >= TIME (from the input record), the device
has undergone an input status change (that is, data is available).
The DATA can then be copied to the device's data-register:
memory[dev * 2 + 1] = DATA;
The 'dba' (data available) and the 'of' (overflow) bits must handled,
first with an overflow check (that is, the dba bit indicates that the
CPU hasn't read the last data value):
if memory[dev * 2] . dba = 1 then
memory[dev * 2] . of = 1
memory[dev * 2] . dba = 1
The next input record read after the current input record is processed.
The above steps are then repeated.
The application program (i.e., one produced from the assembler),
reads a data register by moving the contents of
the device's data register to another location, such as a CPU register:
MOV &Dev1DR,R7 ; R7 = contents of dev 1 data register
The emulator must determine if the device is an input device; if it is,
the 'dba' and 'of' bits must be cleared:
if memory[dev * 2] . io = INPUT then
memory[dev * 2] . dba = 0
memory[dev * 2] . of = 0
Device memory access is detected in bus() (module busmem.c) and the
address is less than DEVICE_LIMIT.
A program can write to an output device by moving a value to the device's
CAP_A EQU 65
As with input, the bus() function determines that the address refers
to a device.
The emulator then writes the character to the register, indicates that the
device is now busy, and starts the device's
This counter is decremented after each instruction is executed; when it reaches
zero, the device is no longer busy and the application program can safely write
another character to the device.
The output from output devices needs to be recorded for testing and to
demonstrate that things are working as expected. This can be done in an
output file, each record containing the time the output occurred
(from sys_clock), the device number, and the output data. The output file can
also be used to record other information, such as when a device processed
its input data or whether an overflow occurred.
For a copy of a polled input and output TI 430 assembler file,
When a device experiences a status change, the information is indicated by
changing its DBA bit (set if an input device, cleared if an output device).
In order to detect this change, the application can poll the device's
status bit, potentially wasting CPU cycles and power, or the device can
signal the CPU directly that a status change has occurred, interrupting
the execution of the application program, allowing, for example, power to be
The CPU needs to determine which device is causing the interrupt.
This can be done by the hardware essentially polling each device to determine
which has interrupted or the hardware can be designed so that the interrupting
device indentifies itself.
Since some devices are considered more important or of higher priority than others,
some machines support priority interrupts, allowing the more important of two
(or more) simultaneous interrupting devices to be serviced first.
A polled-device has a block of code, sometimes a subroutine, which is responsible
for handling the status change.
The same holds true for an interrupting device; however, unlike a polled device,
the application program does not explicitly call the interrupt-handling software.
Instead, a series of memory locations are used, referred to as interrupt vectors,
containing the entry-points (i.e., addresses) of the interrupt-service routines
(or ISRs) for the different devices.
When a device interrupts, the CPU saves the current state of the machine on the
stack and passes control to the device's ISR.
The TI 430 has 32 interrupt vectors, stored in high-memory, starting at
Before considering priority interrupts, test interrupts without regard to
priority to make sure that the interrupt code works to that point.
There's no point in writing the priority code only to discover that neither
interrupts nor priority interrupts work!
Regarding interrupts occurring at the same time or multiple interrupts
accumulating when gie = 0 (i.e., CPU is ignoring interrupts):
To emulate priority interrupts, the input file should be sorted so that any
devices interrupting at the same time occur in the right order.
This is reasonable, since higher priority interrupts are “closer”
to the CPU and therefore occur first when interrupts occur simultaneously.
If multiple devices have status changes (not necessarily interrupts) at the same
time, store the information in devices (this might require additional
fields in the structure such as whether there is a pending interrupt associated
with the device).
Whenever sys_clk >= next_time (inspected in
check_for_status_change()), check devices for the highest
pending interrupt (use a for loop from 0 to NUM_DEVICES).
Pending interrupts should be given priority over non-interrupting devices with
pending status changes (but without a pending interrupt).
The CPU does not run at different priorities -- it either allows (gie is set)
or ignores (gie is clear) interrupts.
If multiple interrupt-enabled devices have simultaneous status changes,
the device with the highest priority interrupt is serviced first; the devices
with lower priority interrupts are ignored until interrupts are re-enabled.
Devices can only have one pending interrupt, which simplifies the design.
If a second interrupt occurs, it overwrites the first and sets the overflow bit
in the device's SCR.
For an example of a simple interrupt-driven input and output (.asm)
for TI 430, click
Contact Dr. Hughes for
more information about this course.