exercise

					     Practical exercise assignments
     Computer Architecture (5B040)
Introduction Computer Systems (2M200)

General remarks
 Unless indicated otherwise (for instance with an 'h' for 'hexdecimal' or a 'b'
  for 'binary'), numerical values in all texts are plain, normal decimal values!

 The "Flashtools" program should NOT be used during these practical
  exercises. The tScope "monitor" has been programmed into the practicum
  processor's Flash-ROM and should remain there.

 The socket holding the 'Phytec' module (containing the actual
  microprocessor) does not fit tight enough. Many problems (like tScope not
  being able to connect to the board) can be solved by pressing the module
  down into its socket. When doing this, support the printed circuit board
  from the backside - NEVER press full force down on the module while the
  circuit board is simply lying on the table!

 The startup instructions given for the first session (starting on the next
  page) must be followed each week.

 Don't forget to switch off the power supplies when you're done!




Practical exercises Computer Architecture 5B040/2M200                    pag 1
Week 2: getting up and running

Task 1: installing and testing of the
          "development environment" on the laptop

1.   Installing programs and documentation

     Before you can do this, you should have downloaded the program
     ‘pracproc.exe’ and the manual ‘pracproc.doc’ from WWW page

     http://www.ics.ele.tue.nl/~ad/comparch/

     Just in case you did not get them, get these files from the network or
     ask for the set of floppies containing them in PK-ZIP format and un-ZIP
     these floppy files (floppies are numbered: start with the highest
     numbered one - PKZIP will ask for the others).

     If you did not do this: install all software and documentation by
     executing pracproc.exe (see section 2.1 of ‘pracproc.doc’ for more
     information).

2.   Connecting the hardware

     In the practial exercise room, some practicumprocessor boards are
     connected in pairs to a single laboratory power supply (others have
     their own 'laptop-style' power sypply, which you can simply plug in). If
     you are working with a board connected to a laboratory power supply,
     do the following in co-operation with your colleagues working with the
     other board:

     Check if the connections between the power supply and the boards are
     made correctly (red wires attach to +8V screw terminals on the boards
     and '+' terminal on the power supply, black wires attach to GND screw
     terminals on the boards and '-' terminal on the power supply).

     For laboratory supplies with an adjustable voltage output, disconnect
     the red wires, turn the supply on and adjust the voltage to
     approximately 8 Volts. If an adjustable current limit is available, set this
     one to approximately 1.0 Ampère. After this, turn the supply off, re-
     connect the red wires to the '+' terminal and turn the supply back on.

     Unadjustabe power supplies (including laptop-style power blocks) can
     simply be turned on / plugged in after checking the connections.




Practical exercises Computer Architecture 5B040/2M200                     pag 2
     After switching on, the ‘Light Emitting Diode’ (LED) labeled ‘+5V’ must
     light up while the LED labeled ‘+24V’ should light up weakly. The state
     of the other 8 LED's is undefined because the flip-flops which control
     them are not forced into a known state upon power-up.

     As we will not be using the power outputs of the practicumprocessor
     board, it is better to move the PWR switch on the board to the '0'
     position.

     Make sure your laptop is switched on and connect the practicum-
     processor board to the laptop using the 9 pin cable (the laptop has just
     one connection which fits, so this cannot go wrong, can it?).

     Just to be on the safe side, (re-)initialise the board by pressing the
     ‘reset’ button (the button closest to the 9 pin connector).

     Now we are ready for the first tests!

     The steps given above must be executed at the start of each practical
     exercises session. If two boards are supplied from one power supply, it
     is definitely not a good idea to switch off the power supply without
     consulting your colleagues - better to use the 'reset' button to get a
     crashed program on the board under control again!

3.   Testing, testing…

     Follow the example walk-through outlined in section 2.3 of
     pracproc.doc (except for the last section 2.3.5). Take at least an hour
     to get aquainted with the development environment (assembler,
     dScope and tScope):

     -      Run the example program in 'single stepping' mode, using
            instruction marking to execute delay loops quickly. Try to
            change the delay loop counters R0 and R1 in order to get
            through these loops more quickly when single stepping (a
            standard method to ‘cheat’ while testing loops). Watch out: it is
            not possible to have a program running when you want to
            change values manually!

     -      Change the ‘RL A’ in the program into an ‘RR A’ and see what
            happens (you can try to make a prediction by looking in file
            c500u.pdf to see what this new instruction does). Make this
            change directly in tScope (using the built-in assembler) as well
            as by changing the original ‘example.asm’ text (via ASM504).

     -      Try to use tScope to directly control the LED’s on the board
            (they are controlled by writing to external memory at address
            0FE00h).

     -      Also try to use tScope to read the state of the switches on the
            board (you can find these in external data memory address


Practical exercises Computer Architecture 5B040/2M200                 pag 3
             0FF00h, repeated through 0FFFFh). You have to double-click in
             the tScope memory window to fetch new values from the board!


Task 2: using the assembler

Dispite the relative simplicity of the ASM504, you can do a lot with it. For the
more complex programs you will write, it is definitely a good idea to make use
of these capabilities. Use labels for all addresses in your program and data
memories and let the assembler calculate the actual addresses for you (just
use the 'ORG' pseudo-op of the assembler to force a specific address).
Thorough documentation of your programs is absolutely necessary! Some
hints and tips (open asm504.pdf for more information):

Use ‘TITLE’ and ‘SBTTL’ to place the name of the program and programmer
      on each page of the listing.

Use segments (with ‘SEG’) to work with separate address spaces. For
     instance, you can place pieces of program and definitions of the
     accompanying data areas together in the program text. For the 'C504,
     you can use the following 'standard' segments:

      ‘program SEG’ for the actual program (and constant tables in there).

      ‘intdata SEG’ for the internal data memory. Do not forget that this
            also contains the working register sets (addresses 0..31), the
            lowest 128 addressable bits (in BYTE addresses 32..47) and the
            stack. Normally, the stack starts after the last address
            specifically allocated here. Unused register set addresses can
            be used for other data, the same goes for unused addresses in
            the bit addressable bytes.

      ‘extdata SEG’ for the external data memory. During these practical
            exercises, only addresses 0FF00h (switch inputs), 0FE00h (LED
            outputs) and 0FD00h (power outputs) will be used!

             Because only a few (fixed) addresses are used, it is not really
             necessary to use a segment here ('EQU' is more convenient,
             see below).

      ‘bits SEG’ for the general purpose bits in internal data memory byte
            addresses 32..47. All directly addressable bits in Special
            Function Registers (SFR's) which have their own name are
            already known to the assembler (same goes for the SFR names
            themselves), so you don't have to define labels for them. All
            SFR's (and the bits in them) are described in c504u.pdf.

Use ‘EQU’ (from 'EQUals') to name constant values (if needed with an
      expression), also to give different names to registers, addresses or bits
      where this can make your program more readable.


Practical exercises Computer Architecture 5B040/2M200                    pag 4
Use ‘DS’/’DEFS’ (Define Storage) to reserve (labeled!) memory addresses for
      storage of information.

Use ‘DB’/’DEFB’/’DW’/’DEFW’ (Define Byte/Word) for placing contstant bytes
      or 16 bit words in the (program!) memory.

Use ‘INCL’ (from 'INCLude') to split a large program into separate texts while
       still retaining the possibility to assemble it in one go.

Use ‘conditional assembly’ (with ‘IF’, ‘ELSE’ and ‘ENDIF’) to skip pieces of the
      program during assembly, depending on a test value or result of a
      calculation. Some special test values (starting with a ‘$’ sign) can be
      used within asm504. A rather complex example of this is the
      ‘drivers.asm’ program which adapts itself completely to the 'user' this
      way.

Task:

Write one or more test "programs" which use these capabilities. These
"programs" need not be executable (they have to end with an 'END', though,
to indicate to the assembler to stop there!). Names you define may not
already be used by the assembler (for instructions, registers, arithmetic/logic
operators, SFR's or SFR bits).

Check the .LST file generated by the assembler to see if the results are what
you expected.

Try to use ‘DB’/’DEFB’ to place a text in the program memory. This text
should be readable in the memory window of dScope after loading this
"program".

Experiment with the arithmetic and logic operations of the assembler and
verify whether or not the results match your expectations. Keep in mind that
addresses for 'ORG' and numbers of bytes for 'DS'/'DEFS' may not depend
upon values defined later in the program (you can try what happens if you do
not adhere to this rule, of course…).

Take your time and compare your results with results from other groups!

        A good rule of thumb is that, by adding sufficient comments, the
        operation of even the most complex program should be clear to other
        programmers…




Practical exercises Computer Architecture 5B040/2M200                     pag 5
Week 3: basic program structures
Just like any other programming language, a program written in an assembly
language contains three basic constructs:

1.     Basic operations to be performed by the processor (moving data
       around and performing simple calculations with these data).

2.     Conditional (i.e. depending on specific testable conditions) execution of
       parts of the program.

3.     Repetetive execution of parts of the program (while a condition is
       satisfied, until a condition is satisfied, a specific number of repetitions
       or just as long as there is power available).

The main differences between assembly language and 'higher' languages
(like Pascal or C) can be summarised as follows:

-      Using assembly language, complex calculations have to be broken up
       into instructions known to the processor. Not all high-level data types
       will be supported directly and intermediate results in a calculation may
       have to be stored and retrieved explicitly.

-      An assembly language programmer has to decide where data will be
       placed in memories and/or registers, also where intermediate results
       will be stored. For the latter, we will try to use processor registers
       whenever possible (in most cases giving faster execution and shorter
       programs). When doing this, we must prevent loading a new value in a
       register when the old value is still needed. This problem of 'register
       allocation' is solved automatically by high-level language compilers -
       when programming in assembly language, a lot of mistakes can be
       made here…

-      Conditional and repeated execution of program fragments is expressed
       in higher programming languages with special constructs (CASE..OF..,
       IF..THEN..ELSE.., REPEAT, WHILE..DO.. etcetera). The instruction
       sequence generated by an assembler is a linear one, however: the
       only method to implement condition tests and repetitions is by using
       'jumps' (loading the program counter with another address than the
       address of the next instruction). This allows us to skip sequences of
       instructions we don't want to execute, it also allows us to jump back to
       the beginning of a sequence of instructions which must be repeated.

Writing a program in assembly language requires more work than writing a
program in a 'higher' programming language because you have to do more
yourself. This does not mean it is more difficult, although it is definitely
advisable to analyse the problem before you start writing a program (using
'trial and error' will not get you there at all or only after a lot of trouble!).



Practical exercises Computer Architecture 5B040/2M200                      pag 6
A good method to organise your thoughts before embarking on the writing of
an assembly language program is to use 'flowcharts', which are built out of
two main elements:



    Operation to               Test         True
     execute                to perform

                         False

The arrows indicate the flow of the program, determining the order of
execution.

Within rectangles, operations to be executed are indicated with expressions
(like 'A := A + 2') or by using an informal description of what should happen
there (like 'load A with the current state of the input switches'). This last
method may describe very complex operations (which may have to be written
out using their own separate flowchart).

Inside a test 'diamond', we will normally write a question which can be
answerred with 'TRUE' or 'FALSE' (in which case we will annotate the
outgoing arrows with labels 'TRUE' and 'FALSE'). The outgoing arrows point
to tests and/or operation rectangles to be executed next, where an arrow
pointing back to an already executed part of the flowchart indicates a
(conditional) repetition.

The starting point of a flowchart is generally indicated with a rounded
rectangle, containing a short description of the function of this flowchart. If the
flowchart has a specific ending point (where the function is completed), then
this ending point will also be indicated (for instance using a rounded rectangle
containing the word 'end').

The main rationale for using flowcharts is to have a structured and easy to
read method for showing the operation of a program. In general, flowcharts
are worked out to such a level of detail that each main element can be
implemented with a maximum of 5 to 6 processor instructions (for beginners,
preferrably somewhat less, say 2 to 3). In this process, the (twodimensional)
flowchart has to be converted into a linear sequence of instructions. This can
be visualised by placing all rectangles and diamonds in a single column, after
which you can check which arrows run vertically alongside this column.
Wherever such an arrow points inside the column, that is a spot where an
address label will be needed, with a jump instruction referring to this address
label at the point where the arrow started.

The next flowchart calculates the Fibonacci series (i(N) = i(N-1) + i(N-2), with
i(0) and i(1) equal to 1), where an overflow during the addition will restart the
series right back from the start. It is our intention to convert this program into a
program which can run in the dScope simulator. Observe that this flowchart
has already been drawn in a single column.



Practical exercises Computer Architecture 5B040/2M200                        pag 7
                                   Tasks:
           Fibonacci               -     Check whether or not this flowchart
                                         contains an algorithm to calculate the
          Current := 1                   values in a Fibonacci series (for
          Previous := 1                  instance, where is the value 'i(N)'
                                         stored?).
      Temp := Previous             -     Using section 4.1 of the manual,
     Previous := Current
                                         decide where you want to store the
                                         three values which are used here
         Current :=                      (Current, Previous and Temp). Good
       Current + Temp                    choices would be the working registers
                                         and/or the internal data memory
                                         (watch out: the latter is used for more
            Overflow                     than one purpose!).
            on '+' ?       False
                  True             -  Using section 4.2 of the manual, find
                                      sequences of instructions which
      implement the operations called for in the rectangles. You may find you
      will have to use the 'A' register because a direct execution is not
      possible (this is also the main reason not to use the 'A' register for
      storing data!).

The two outgoing arrows from the diamond point back into the flowchart, so
they have to be implemented with two separate jump instructions (from
section 4.2.5 of the manual). Of these two, the first one should test the actual
condition and jump only if this condition turns out to be true, the second one
can be an un-conditional jump following the remaining arrow.

-     The add instruction modifies both the 'carry' and 'overflow' flags. Which
      of these two would be the best choice here? Now select the conditional
      jump instruction matching the chosen test (the 'overflow' flag is a
      directly testable bit, known to the assembler under the name 'OV').

-     Write the complete program in assembly language, using the
      'framework' given in section 4.1.1 of the manual:

      -        Place the instruction sequences in the correct order.

      -        Insert address labels at the points where jump instructions are
               pointing to and use these labels within the actual jumps.

      -        For a more readable program, insert comments and attach
               names to data storage locations. This can be done for working
               registers (for instance: 'Temp EQU R2', to be read as 'define
               "Temp" as extra name for register R2') as well as memory
               locations (for instance: 'Temp: DS 1', meaning 'at the current
               address labeled "Temp", Define Storage for 1 byte'). For the
               latter case, the ORG pseudo-operation of the assembler must
               be used to initialise the 'current address' in the internal data


Practical exercises Computer Architecture 5B040/2M200                    pag 8
             memory. The 'SEG'-ment capabilies of ASM504 may also be
             helpful here.

-     Assemble your program and check in the .LST text whether or not the
      result is correct (especially, check addresses of stored values and the
      actual placement of the program itself).

-     Run (with single stepping) your program with dScope… Good luck!

Other tasks (if you got some time left):

-     Modify the flowchart and (after that!) the program in such a way that
      i(N) is calculated (or the lowest 8 bits of the value of i(N) ), when N is
      supplied in the A register. Keep in mind the special handling of values
      i(0) and i(1) and use a counted loop (remember the 'DJNZ' instruction!)
      for higher values of N. Place the result of the calculation in the A
      register.

-     Extend this program so that N is read from the switches (at address
      0FF00h in the external data memory) and the result will be displayed
      on the LED's (at address 0FE00h in the external data memory). Put all
      of this in an endless loop so that you can 'play' with the switches
      (watch out: a closed switch gives a 0 bit, an open switch gives a 1 bit
      and switch number 1 gives bit number 0 of the byte you read).

For the very fast programmers:

-     Write a program which converts bits 0..3 of the switches into the
      accompanying ASCII code for the hexadecimal digit (use the table in
      Tanenbaum's book) and shows this ASCII code on the LED's. Hint: the
      simples solution uses a table in program memory (which can be read
      with a 'MOVC' instruction).

-     Write a program that determines which of the switches in the 'on' state
      has the highest number and illuminates the corresponding LED.




Practical exercises Computer Architecture 5B040/2M200                    pag 9
Week 4: basic I/O and subroutines
Subroutines facilitate the modularisation of- and easy access to often used
functions or operations within a program. A subroutine itself is a normal
program fragment with a recognisable begin- and endpoint. Starting the
execution ('calling') of a subroutine is done with a special jump instruction
loading the starting point of that subroutine into the Program Counter (use an
address label!). In addition, this special jump instruction PUSHes the address
of the instruction following the jump instruction itself onto the stack. At the end
of the subroutine is another special jump instruction to return to the calling
program fragment. This 'return' instruction POPs the address of the instruction
following the 'call' from the stack and loads this address into the Program
Counter.

In assembly language, the information needed by a subroutine for its
execution will normally be placed in processor registers. If too much data has
to be provided, it can also be stored in memory with the starting address of
the data in a register. Often, information is passed via the stack (PUSHed
onto it just before the 'call' instruction is executed).

Execution results of a subroutine will in general be placed in registers (easy to
check by the calling program fragment), although it is possible to use memory
locations too. If the result is a Boolean true/false value (for instace the result
of a complex test), then it is easier to use -testable!- processor status flags to
indicate this result.

For the ease of use of a subroutine, it is advisable to 'safeguard' processor
registers and status flags which are going to be changed by the subroutine
but which will not be used to indicate result values. With 'safeguarding' we
mean here that the original values will be restored at the end of the
subroutine. The simplest method to do so is to make use of the stack: PUSH
the affected registers and flags onto the stack at the start of the subroutine
and POP them off the stack again at the end (in reverse order!).

NOTE: within a flowchart, a special symbol is used for
calling a subroutine (rectangle with extra vertical lines). The
subroutine itself is generally given as a separate flowchart               Read
(using rounded rectanges for begin and end symbols).                      switches

Remarks regarding the use of subroutines within the
practicumprocessor:

   There are two 'call' instructions available: 'LCALL' (can target a subroutine
    anywhere in the 64 kilobyte program memory) and 'ACALL' (is just like
    'AJMP' limited to addresses within the 2 kilobyte page in which the
    instruction following the 'ACALL' starts).

   There is only one 'return' instruction, appropriately called 'RET'.


Practical exercises Computer Architecture 5B040/2M200                        pag 10
   Both 'PUSH' and 'POP' only work with direct addresses, so:
     Working registers R0..R7 have to be accessed via their direct
      addresses in the internal data memory ('PUSH R0' will NOT work).
     The 'A' register can be PUSHed and POPped using its Special Function
      Register name 'ACC'.
     Both 'B' and 'PSW' registers can be accessed under their own name as
      SFR.
     The 16 bit 'DPTR' register is accessable as two 8 bit SFR's: 'DPH' (bits
      8..15, 'Data Pointer High') and 'DPL' (bits 0..7, 'Data Pointer Low').

   If only a single byte value has to be returned from a subroutine call, it is
    advisable to use the 'A' register. Try to avoid using the R0 en R1 registers -
    these are the only ones which can perform indirect addressing (and
    generally contain pointers). Passing a single Boolean value is easiest
    using the carry status flag.

   Using the stack to pass values into- and out of a subroutine is awkward on
    the practicum processor because it is fairly difficult to address data stored
    inside the stack.

   Placement of subroutines in an assembly language program text is not
    bound by any rule. Sticking to the 'immportant things first' notion will make
    the program more readable by placing the main program fragment (or
    routine) up front, followed by subroutines which are directly called by this
    program fragment, with the 'lowest level' subroutines at the very end.
    Every subroutine should be provided with comments indicating its purpose
    and 'interface' (how values must be provided and will be given back). You
    should also indicate which registers and/or flags are changed if these are
    not used to return result values. The next remark should be superfluous:
    choose a good descriptive name for the address label at the start of a
    subroutine!

   A nice trick: place often used standard subroutines in separate .ASM text
    files (which should all have their own 'END' line). The contents of such a
    text file can be inserted into other .ASM texts using the ASM504 pseudo-
    operation INCL '<filename>' (the contents of the included file will
    actually replace the line containing the 'INCLude' statement during
    assembly). This allows you to create a complete library of standard (and
    hopefully operational ) subroutines.

Tasks:

   Write two 'nice' subroutines (which safeguard registers and flags where
    needed). One should show the contents of the A register on the LED's
    (address 0FE00h in the external data memory), the other should read the
    state of the switches (from address 0FF00h in the external data memory)
    into the A register. Test these routines with a program which reads the
    switches and shows their state on the LED's (in an endless loop, of


Practical exercises Computer Architecture 5B040/2M200                     pag 11
    course!). It is probably a good idea to place both subroutines in separate
    .ASM text files as indicated above.

   Write a subroutine (again, a 'nice' one) which returns the state of switch
    number 1 in the carry flag (switch open: carry = 1b, switch closed:
    carry = 0b). This subroutine can use the subroutine for reading the
    switches you wrote for the previous task - switch number 1 will be placed
    in bit number 0 of the A register (a directly addressable bit using the name
    'ACC.0').

   Now, write a subroutine which waits until switch 1 is closed before
    returning (this routine does not return any actual result value!). The only
    correct way of doing this is to first wait until the switch is open(ed), then
    wait until it is closed, after which the routine returns. Draw a flowchart first
    and use the subroutine which tests switch number 1.

   Finally, write a test 'program' to evaluate this last subroutine: use the
    LED's to show a binary count of the number of times the switch has been
    closed since the start of the program. Try to solve this problem yourself
    before reading the solution given in the bottom-right corner of this page!

When testing this program, you will find that the counter sometimes (or even
often) skips counts when closing the switch. This is normal - there is no
mistake in the program, it is a nasty habit of the switch itself. When




                                                                             closed (subroutine!), increment the counter (using the 'INC' instruction) and then
                                                                             Soution for the test program: start by loading a counter register (for instance A)
                                                                             with 0, display the contents of this counter on the LED's, wait until the switch is
closing the electromechanical contact, the mechanism bounces a
littlebit, creating extra open-closed transitions. Your test program is
more than quick enough to count these extra transitions…

The solution: deliberately slow down the program so that it will wait
until the contact has stopped bouncing.

   Write a subroutine which returns after a delay of approximately          go back to the point where the counter is displayed on the LED's.
    5 milliseconds (need not be exact to the microsecond). You
    can base this one on the delay loop in the 'running light
    example' shown in section 2.3.1 of the manual.

   Modify the subroutine which waits for the closing of the switch:
    after a first detection of the closing, wait 5 milliseconds and
    then check again if the switch is still closed. If not, start all over
    again. If the switch turns out to be closed after the waiting
    period, the routine can return. Use a flowchart!

With this safeguard, the counter of the test program should not
skip counts anymore each time the switch is closed.

Furhter tasks (in case you have some time left…):

Another solution to the problem of the bouncing switches is to use
swtches with two sets of contacts. One of these is closed when the
switch is activated (the 'normally open' contact), the other set is
closed when the switch is NOT activated (the 'normally closed'


Practical exercises Computer Architecture 5B040/2M200                                 pag 12
contact). These sets of contact are coupled mechanically in such a way that it
is impossible that both sets are closed at the same time.

   Draw a timing diagram showing the open/closed states of both sets of
    contacts for such a switch when it is activated and de-activated. Don't
    forget to include some switch bouncing in your diagram! Can you come up
    with a method to determine the moments of activation and de-activation?
    Hint: think of the behaviour of a simple 'set-reset' flip-flop!

   Assume that switch number 1 on the practicum processor board is the
    'normally open' contact of a button switch (will be closed upon activation)
    and that switch number 2 is the 'normally closed' contact (which will be
    opened upon activation). Now, write a new subroutine which will wait until
    (1) the button switch is NOT ativated and then until (2) the button switch IS
    activated. Modify your test program to call this subroutine and check
    whether this modification prevents counter skips (don't cheat with the
    switches!).

       For easier testing, we will provide some real push buttons with double
       contact sets in the practical exercises rooms…

And to fill the time for the really quick programmers:

Create some new running lights, if needed with a group of students…

   Start with one where four switches can be used to select the speed and
    one switch controls the direction of running, but where there is still just one
    LED illuminated at the same time.

   Extend this one running light into one where the remaining three switches
    control the amount of LED's illuminated at the same time - this assignment
    is rather complicated (well above exam level!).



A remark regarding professional 'debouncing' of switches:

An often used method reads the real switches at a fixed repetition rate (for
instance every 10 milliseconds), storing the last two values read. It is then
possible to mimic a set of debounced switches in a memory location which
only change state if the last two states of the real switches were the same.
This functionality can be performed by an interrupt routine which is called
periodically (using a hardware 'timer'), relieving the main program from the
debouncing task.




Practical exercises Computer Architecture 5B040/2M200                      pag 13
Week 7: some more complex programs
As we have only a limitied amount of time during these practical exercises,
we cannot work on really big problems. Still, we can work on interesting
problems by working together in larger groups. The tasks for this week are
definitely above exam level - we do not expect an individual student to finish
the assignments given this week within one hour.

In lecture week 6 we have shown some methods to transfer data within
computer systems. The practical work assignment of this week is to transfer
data between two practicum processor boards. Two groups of students, each
equipped with one practicum processor, will have to work together to
complete the assignments of this week (these boards cannot be located far
from eachother!).

The actual data transfer will take place using wire connections between
parallel ports P1 (bits 0..7) and P3 (of which only bits 2..5 are connected to
screw terminals). If the two communicating practicum processor boards are
connected to the same power supply, these data connections suffice. If the
boards have different power supplies, an extra wire connection has to be
made between 'GND' terminals on both boards (doesn't matter which one you
use).

Ports P1 and P3 are directly addressable 'Special Function Registers' known
by the assembler under these names. P1 and P3 are also bit-addressable.
The electronic circuits of the processor pins make these ports 'quasi-
bidirectional', meaning that they are both inputs as
well as outputs. To us, this gives the effect of a      reading       reading
logical 'AND' gate: we will read a binary 1 at the
                                                     Port pin           Port pin
connected port pins only after we have written a
                                                     at proc. 1       at proc. 2
binary 1 to both of them. Both sides will read a
binary 0 if one or both the pins have been written      writing        writing
with a binary 0.

Bits 0, 1, 6 and 7 of port P3 are used for special purposes in the practicum
processor and may never be written with a binary 1. To prevent problems, it is
better not to use port P3 at all this week…

Task (only an introduction, read on!):

   Connect two practicum processors using a maximum of 3 (THREE) port
    pins on each of them, in such a way that the state of the 8 switches on the
    'transmitting' processor is made visible on the 8 LED's of the 'receiving'
    processor.

This is a fairly open task! Because we are only allowed to use three port pins,
it is impossible to transfer the information in a parallel way (would be a bit too
easy anyway…). Below, we will show four examples of communication



Practical exercises Computer Architecture 5B040/2M200                     pag 14
protocols which all solve the problems of transferring separate bits and
indicating the start and end of a group of bits.

Work together! Within your group, choose one of the protocols and design
the global method of operation of both the transmitter and receiver (use
flowcharts!). Having done this, write the actual transmitter and receiver
programs in sub-groups and cross check eachother's work before you start
testing.

First some general remarks:

    Wire is expensive (well, expensive…). What we want to say here is that, in
     general, protocols will be chosen which use a minimal amount of wires. In
     most cases, these are also the more complex protocols. Keep this in mind
     when choosing your protocol!

    The transmitter can choose to transmit the states of the switches
     continuously or only after a state change. The latter is more interesting but
     also a littlebit more complicated.

    To ease the debugging, it may be a good idea to show the values placed
     on the P1 port on the LED's too (and create the possibility to send veeeery
     slowly)…

Protocol number 1 using three wire connections:

This one splits the functions of data transfer, bit timing and byte timing across
separate wires. The transmitter just makes sure to send slowly enough for the
receiver to follow (include delay loops!). Port pins chosen here are just an
indication:
P1.2 ('byte')

P1.1 ('bit')

P1.0 ('data')      bit 0   bit 1   bit 2   bit 3   bit 4   bit 5   bit 6   bit 7


A 1-0 transition at the 'byte' wire indicates the start of a new byte, after which
every 1-0 transition at the 'bit' wire indicates to the receiver to read a single bit
from the 'data' wire. After transferring the last bit, the 'byte' wire becomes 1
again (and it should stay that way long enough for the receiver to detect this!).

Protocol number 2 using two wire connections:

You could re-use protocol 1 without the 'byte' wire, but then the receiver may
not be able to figure out the start of a new byte after it has lost a bit for some
reason. A solution is to let a timer run during the waiting for a 1-0 transition on
the 'bit' line. If the measured waiting period is longer than a single bit time, the
receiver may decide that the last bit must have been the last one of a byte (so
the next bit should be the first one of a new byte).



Practical exercises Computer Architecture 5B040/2M200                              pag 15
The problem with this protocol is that the transmitter must be able to keep up
with a minimum bit speed. If the transmitter is slowed down too much, the
receiver will think that a new byte has been started.

Protocol number 3 with two wire connections:

This is a variation on protocol 1 too, only this time the transitions on the 'bit'
line are shifted in such a way that transitions on the 'data' line happen only
when the bit line is at binary 0. The start of a byte is indicated by a 1-0
transition on the 'data' line while the bit line is at binary 1 (against the rule in
the previous sentence!):

P1.1 ('bit')

P1.0 ('data')          bit 0     bit 1       bit 2       bit 3       bit 4    bit 5       bit 6       bit 7


Pay attention to the fact that the last 'data' line transition at the end of bit 7
happens while the 'bit' line is at binary 0. The last 1-0 transition of the 'bit' line
does NOT indicate that data is to be read (as indicated for bit number 0).

Hint for programming the receiver of this protocol: wait for the reception of a
data byte by waiting for a 'data' line transition to occur while 'bit' is binary 1.
Following this, 8 data bits can be read on successive 1-0 transitions of the 'bit'
line (check for the validity of this reasoning!). As always, the transmitter must
transmit slow enough for the receiver to keep up with it…

Protocol number 4 with one wire connection:

If only a single wire connection is left, we have to let the factor time play an
important role. The transmitter must send the information at a specific speed
because the receiver will not get a separate signal indicating the transmission
of the next data bit. The receiver must know the transmission speed in order
to 'sample' the values on the data line at the correct time points. The standard
method of 'asynchronous' serial communication as given during the lectures is
very well applicable here:

                    1.5                                                                        


P1.0 ('data')            bit 0       bit 1       bit 2       bit 3    bit 4       bit 5       bit 6       bit 7


                bit-time 

Hints for programming this protocol: Use a delay loop subroutine which gives
a delay equal to 0.5 , and don't chose this delay too short (for instance,
chose 0.5 milliseconds) - this allows you to neglect the execution time of the
remainder of the program. After detecting the initial 1-0 transition (of the 'start
bit', the receiver should check after 0.5  to make sure that the value is really
a binary 0. Following the last data bit, the transmitter should keep the 'data'
line at binary 1 for at least 1  period!



Practical exercises Computer Architecture 5B040/2M200                                                             pag 16
If there is still some time left:

   Go and find another group who have chosen the same protocol and check
    whether or not their transmitter can communicate with your receiver (and
    the other way around).

   Try another protocol for a change!

And for the really very quick programmers:

   A much more complex task is to try to transmit and receive at the same
    time (transmitting switch states in both directions at the same time). This is
    a complex task even if separate wire connections are used, just because it
    is difficult to transmit and receive at the same time. It is recommended to
    come up with a special protocol which synchronises the transmissions
    from one of the connected practicum processors. This assignment is far
    above exam level, but it is achievable!




Practical exercises Computer Architecture 5B040/2M200                     pag 17
Week 8: interrupts and coroutines
Interrupts:

Chapter 5 of the practical exercises manual describes how to use timers to
generate interrupts at fixed time intervals. Try to create a running light which
does not use a delay loop for its timing, but which initialises such a timer and
then 'runs' the running light inside the interrupt routine.

   Your main program should end in an endless loop (which is interrupted
    periodically by the interrupt routine).

   Make sure your interrupt handler is transparent (or 'nice')! Use a byte in
    internal data memory to store the state of the running light.

   If the running light runs too fast and it is impossible to slow down the timer
    (which will probably be the case), use an extra counter in an internal data
    memory byte to step the running light every N interrupts. During the other
    N-1 interrupts, the state of the running light should not be changed. You
    can choose the constant value of N yourself…

Coroutines:

Coroutines don't use normal 'call' and 'return' instructions, but rather use a
special 'exchange' instruction which jumps between different programs. The
practicum processor does not know a real 'exchange' instruction (at least not
one with coroutine behaviour), but we can mimic one using a subroutine.

When calling a subroutine, the program counter (PC) is pushed onto the stack
(first the least significant 8 bits, then the most significant 8 bits). This is the
return address of the subroutine (the address of the first instruction following
the call instruction).

A subroutine which mimics the 'exchange' instruction has to swap this
subroutine return address with the return address of the second coroutine. It
is a good idea to reserve two memory locations to hold this other 16 bits
address. To perform the swap operation, you will need two temporary byte
storage locations. It is possible to use working registers for this purpose, but
that would give some trouble to make the 'exchange' subroutine into a 'nice'
one (you would have to save and restore them). Fortunately, the POP
instruction operates with direct addresses, so you can pop the return address
from the stack directly into (temporary) internal data memory locations,
followed by pushing the second coroutine address onto the stack. After this,
use direct data moves to copy the temporary address storage contents into
the 'second' coroutine address storage - ready for the next 'exchange' call.
Make sure that the memory locations you use are really free and not used by
the stack or other data.




Practical exercises Computer Architecture 5B040/2M200                      pag 18
Before starting the first coroutine, you will have to load the 'second' coroutine
address storage locations with the actual starting address of this coroutine!
This must be done with immediate data moves into memory, one byte at a
time. You can use the assembler to split the address of this second coroutine
into high and low bytes using the HIGH(label) respectively LOW(label)
functions. After setting this address, the first coroutine can simply start (you
do not even have to call it, the running program 'becomes' the first coroutine).

Now, write the actual 'exchange' subroutine and two coroutines which call
eachother via this subroutine:

Coroutine 1 shows a 4 bit binary counter on LED's 0..3.
Coroutine 2 shows a running light on LED's 4..7.

One of the main problems is that both coroutines jointly have to control a set
of 8 LED's. The solution is to give each coroutine control of its own 'virtual'
LED's in a separate memory location and write a subroutine to combine these
memory locations and show them on the actual LED's.


And for the extremely quick programmers:

Rewrite last week's transmitter side of your communication link to use
interrupts for its timing (with one protocol step -line transition- for each
interrupt).

NOTE: this task is far above exam level! The main problem is that you will
have to create a kind of Finite State Machine in your program to keep track of
which protocol step is to be executed on the next interrupt. The following
might sound strange, but protocol number 4 is actually the simplest one to
implement this way (it has the lowest number of line transitions).




Practical exercises Computer Architecture 5B040/2M200                    pag 19
Week 9: a miniature "operating system"
In the lectures, the operating system is introduced as a set of procedures and
subroutines which ease I/O operations and multitasking within a computer
system. For the tasks of this week, we provide a miniature operating system
which can 'time-slice' multiple tasks running on the practicum processor.
These tasks all seem to run at the same time and can each perform their own
operations. Below, we give some introductory documentation to this 'time-
slicer'. The assembly language code (with extensive comments) can be found
in the file 'slicer.asm'.

To perform time-slicing with the provided 'operating system', the following
requirements have to be met:

1. You should create space in the internal data memory for an administration
   table (using 'Define Storage'). This table should start with 4 bytes for the
   slicing process itself and the first (main) task. Each additional task to be
   run will require an extra 2 bytes in the table. The starting address of the
   table must be located below address 127 and should be labeled
   _tasktab. The 'slicer.asm' file can not be INCLuded into your program
   before this table has been defined.

2. Every task (including the main task) should have its own stack space. This
   means that for eash task i you have to reserve a memory space with size
   N in the internal data memory with 'stacki: DS N'. Each of these stack
   spaces should be at least 13 bytes in size, if subroutines are called this
   space should be increased to store extra return addresses.

3. The tasks can all freely use the registers SP, A, B, PSW and DPTR (these
   are all saved at each task switch), but they have to share the working
   registers R0..R7 (more on this later).

4. Before starting the actual tasks, subroutine _inittasks has to be called
   to initialise the slicer and start of the administration table. Every process
   has to register itself properly before starting up by performing the following
   operations:

      The stack pointer has to be loaded with the starting addres of the stack
       memory space MINUS 1 (use 'MOV SP,#(stacki-1)').
      The data pointer must be loaded with the starting address of the next
       task's initialisation (use 'MOV DPTR,#nextTaskLabel'). An exception
       forms the last task, which is free to do as it pleases with the data
       pointer.
      If no further tasks have to be registered, subroutine _strtslice
       should be called. To register a new task, subroutine _newtask must
       be called. _newtask does not return immediately, but rather
       continues with the initialisation of the next task(s). Only when the last


Practical exercises Computer Architecture 5B040/2M200                    pag 20
       task has called _strtslice will the tasks be started up at the
       addresses immediately following the calls to _newtask (a 'delayed
       return' mechanism).

When these requirements are met, the slicer will automatically run all tasks in
a time-shared fashion (starting from the call to _strtslice).

In last week's coroutine task, we have seen that is it awkward to control the
LED's with different tasks. The slicer offers a convenient mechanism we can
use here.

If we reserve a directly addressable memory location labeled_ledbuf before
INCLuding the ‘slicer.asm’ file, the slicer will automatically copy this location to
the LED's on each task switch. We only have to make sure this memory
location gets the correct values to be shown on the LED's. To control the
LED's individually, it is a good idea to place _ledbuf at a bit-addressable
location (bytes 32..47 -decimal!- of the internal data memory).

Because working registers R0..R7 have to be shared by all tasks, we are
threatened by a shortage of them. Fortunately, the practicum processor has
four separate sets of these registers. Bits 3 ('RS0') and 4 ('RS1') of the PSW
register control which set is used (initially set 0, with bits 3 and 4 both 0b). By
selecting different sets of register for different tasks, up to four tasks can use
8 working registers each. Watch out: subroutines shared by different tasks
can not simply use 'push i' to push register Ri onto the stack anymore!

Task 1:

       Use the time slicer to run three separate processes, each of which
       blinks a LED on and off at slightly different speeds.

The SAB-C504 processor knows an instruction which is suited to implement a
simple kind of 'binary' semaphores. This is the 'JBC bit,rel' (Jump if direct
Bit is set to 1b, always Clear direct bit to 0b) instruction mentioned in section
4.2.5 of the manual. This instruction allows the testing and resetting of a
single bit in one single, undividable operation. It essentially allows the
'decrementing' of single bit semaphores. If the semaphore had the value 1, a
jump is performed and the value is decremented to 0. If the semaphore had
value 0, the semaphore has to be re-tested with the JBC instruction until it is
found to have value 1. This will 'block' the testing task. Incrementing of the
single bit semaphore can be performed with the standard SETB instruction.

Task 2:

       For this task, we need two semaphores, let's call them X and Y (can be
       two different bits in a single bit-addressable memmory location). X has
       as initial value 1 while Y has as initial value 0. Now we need two tasks,
       which operate as follows:

       1. This task decrements semaphore X and then displays a running
          light on the LED's running from left to right - but does this only once.


Practical exercises Computer Architecture 5B040/2M200                       pag 21
         When finished, semaphore Y is incremented and this task starts
         again, trying to decrement semaphore X.

      2. This task is the mirror-image of the first task: it tries to decrement
         semaphore Y and then displays a one-time-only running light on the
         LED's running from right to left. When finished, semaphore X is
         incremented and this task starts again, trying to decrement
         semaphore Y.

Extra task for extremely quick programmers:

      Try to achieve the same effect as the last assignment, but now without
      semaphores and using just a single bit for communication between the
      tasks.




Practical exercises Computer Architecture 5B040/2M200                  pag 22

				
DOCUMENT INFO