[comp.lang.ada] Memory-mapped I/O

harold@harvax.UUCP (Harold Rabbie) (01/17/90)

It seems that there is a misconception about the use of data representation
specifications to do memory mapped I/O in embedded systems. A lot of people
write code like: 

    type DEVICE_TYPE is range 0 .. 255;
    for DEVICE_TYPE'SIZE use 8;            -- some 8-bit hardware register
    DEVICE_REGISTER : DEVICE_TYPE;
    for DEVICE_REGISTER use at 16#1234#;   -- memory mapped address

and  then  use Ada assignment statements with the object DEVICE_REGISTER to
do  memory-mapped  I/O. For example, on a bare Motorola 680X0 you do I/O by
accessing  locations  in the memory space. The object DEVICE_REGISTER is of
course  a  special  piece  of hardware inside the I/O device, and not a RAM
location at all. 

The  problem  is that most compilers assume that data objects are stored in
RAM  memory,  and  so  they  may  do  things that work fine in RAM, but are
inappropriate for device registers. 

Many  devices are sensitive to the size of access, whether 8, 16 or 32 bits
wide. Just because I give a SIZE representation clause to an object doesn't
guarantee  that  the compiler will emit a MOVE instruction of the specified
width  when  accessing that object. It's perfectly entitled to use whatever
instructions  it  deems appropriate, as long as it gets the right result in
normal RAM memory. 

For example, if the device is located at an odd address, the generated code
might  read  the next lower even address and mask out the unnecessary bits.
That  is a valid sequence for a RAM location, but probably wouldn't work on
an I/O device register. 

Another  behavior  that  works  just  fine  for RAM is to initialize all of
memory  to  zero before the program starts. This could easily screw up many
devices. 

I  believe  the  right  way  to do physical I/O is through the LOW_LEVEL_IO
package  defined  in LRM 14.6. For example, you could define the package to
do memory-mapped I/O of different widths as follows: 

package LOW_LEVEL_IO is

  subtype DEVICE_TYPE is new SYSTEM.ADDRESS;	-- for memory-mapped I/O
  procedure SEND_CONTROL   ( DEVICE : DEVICE_TYPE; DATA : in ....
  procedure RECEIVE_CONTROL( DEVICE : DEVICE_TYPE; DATA : out ....

end LOW_LEVEL_IO;

The   procedures   SEND_CONTROL  would  do  memory-mapped  output  and  the
procedures  RECEIVE_CONTROL  would  do memory-mapped input. You specify the
width (8, 16 or 32 bits) by the type of the DATA parameter. 

Then  you  write  the  body  of LOW_LEVEL_IO with machine-code insertion or
pragma  INTERFACE(  ASSEMBLER)  to get the right instruction sequence. This
also  has  the benefit of portability to embedded architectures that do not
use  memory-mapped  I/O,  for  example, the Intel 80X86 family. And if your
compiler can inline assembly code, you're in fat city. 

I'd  be interested in hearing the opinions of the Ada community on this. If
people  really want to do memory-mapped I/O with Ada assignment statements,
then  somehow  we'll  have  to  tighten  up  the  requirements  on the code
generated by the compilers. 

--------------------------+--------------------------------------------
Harold Rabbie, Ready Systems     "when REAL_TIME => accept READY_SYSTEMS;"
UUNET: {sun!pyramid,hplabs}!harvax!harold   ARPA: rabbieh@ajpo.sei.cmu.edu
--------------------------+--------------------------------------------

NCOHEN@IBM.COM ("Norman H. Cohen") (01/18/90)

I agree with Harold Rabbie:  The best way to do memory-mapped I/O is
through LOW_LEVEL_IO.  If your compiler does not have an appropriate
SEND_CONTROL procedure, the best alternative is a code procedure.
This is one of the few circumstances in which a code procedure is both
appropriate and palatable.  It is appropriate because the intent is to
emit a specific short sequence of machine-code instructions.  It is
palatable because the sequence is only one instruction long.

A case study that SofTech did for CECOM (Ft. Monmouth) in 1982 pointed
out another problem, in addition to those mentioned by Rabbie, with the
use of assignment statements:  An assignment to a variable that is
located at an I/O address is typically not followed by a reference to
that variable.  Thus the variable looks "dead" to data-flow analysis and
the assignment is likely to be eliminated as useless by an optimizing
compiler.  (A strong case can be made that a compiler for a machine with
memory-mapped I/O must never treat an assignment as useless if the
target variable is subject to an address clause, but a case can also be
made to justify this behavior.)

Norman Cohen