[comp.lang.ada] Types defining h/w r/o or w/o registers

gardner@titan.tsd.arlut.utexas.edu (Don Gardner) (05/30/91)

Is there some way to define a type such that all variables of that
type can be only read or only written?  For instance, I have a type that
defines the status register of some peripheral which can only be 
read.  It would be best if the compiler could flag any assignments 
to variables of such a type as errors.

Is there some other means for commonly handling this problem?  The 
compiler being used is VADSWorks, if an implementation-defined solution
is required.

willett@cbnewsl.att.com (david.c.willett) (06/01/91)

In article <3949@titan.tsd.arlut.utexas.edu>, gardner@titan.tsd.arlut.utexas.edu (Don Gardner) writes:
> Is there some way to define a type such that all variables of that
> type can be only read or only written?  For instance, I have a type that
> defines the status register of some peripheral which can only be 
> read.  It would be best if the compiler could flag any assignments 
> to variables of such a type as errors.
> 
> Is there some other means for commonly handling this problem?  The 
> compiler being used is VADSWorks, if an implementation-defined solution
> is required.

Don,
	The Ada LRM defines a "limited private" type as being one for which
the assignment operators are not valid outside the package in which the 
type is declared (see Booch, "Programming in Ada" pg. 125).  I think that
will take care of the read only case.  Only suggestion I have on write 
only is to hide the write operation in a package and provide no 
corresponding read function.

			Good Luck,
				Dave Willett

willett@cbnewsl.att.com (david.c.willett) (06/01/91)

In article <1991May31.170353.21919@cbnewsl.att.com>, willett@cbnewsl.att.com (david.c.willett) writes:
> In article <3949@titan.tsd.arlut.utexas.edu>, gardner@titan.tsd.arlut.utexas.edu (Don Gardner) writes:
> > Is there some way to define a type such that all variables of that
> > type can be only read or only written?  For instance, I have a type that
> > defines the status register of some peripheral which can only be 
> > read.  It would be best if the compiler could flag any assignments 
> > to variables of such a type as errors.
> > 
> > Is there some other means for commonly handling this problem?  The 
> > compiler being used is VADSWorks, if an implementation-defined solution
> > is required.
> 
> Don,
> 	The Ada LRM defines a "limited private" type as being one for which
> the assignment operators are not valid outside the package in which the 
> type is declared (see Booch, "Programming in Ada" pg. 125).  I think that
> will take care of the read only case.  Only suggestion I have on write 
> only is to hide the write operation in a package and provide no 
> corresponding read function.
> 
> 			Good Luck,
> 				Dave Willett
> 

Silly me, I listed the wrong author in the above post.  As we all know,
Grady Booch wrote "Software Engineering with Ada".  It was J.G.P. Barnes
who wrote "Programminng in Ada".  My appologies to all concerned.

				Dave

jduarte@liege.ICS.UCI.EDU (J o s e D u a r t e) (06/01/91)

> Is there some way to define a type such that all variables of that
> type can be only read or only written?  For instance, I have a type that
> defines the status register of some peripheral which can only be
> read.  It would be best if the compiler could flag any assignments
> to variables of such a type as errors.

> Is there some other means for commonly handling this problem?  The
> compiler being used is VADSWorks, if an implementation-defined solution
> is required.


Well...Ada is "the" language which explicitly allows you
to name variables which can ONLY be read/written/read-write using
the procedure syntax:


with REGISTERS; use REGISTERS;
procedure XYZ(Reg1: in REGISTER; Reg2: out REGISTER; Reg3: in out REGISTER) is
begin
  -- Reg1 can only be used effectively as a constant in this scope
  -- Reg2 can only be assigned to in this scope
  -- Reg3 can be assigned to or read from in this scope
  null;
end XYZ;

.
.
.

Alternatively, you can define a "type" and then define a constant variable
of that type for read only variables...

subtype XYZ is INTEGER;
NY_REG: constant XYZ := 0; -- define a  variable that can only be read

.
.
.

to define a type in a package that does not have ":="
implicitly defined...use a "limited private" syntax:
package REGS is

type XYZ is limited private;

-- insert your procedures/functions here

private
type XYZ is NEW WHATEVER;
end REGS;

--- This would not allow other programmers that use your package to assign
--- to a variable of type "XYZ" unless you also specified an "Assign" procedure.
--- Look up "limited private" types for more details. I believe that "limited
--- private" means that "=" (and thus "/=") and  ":=" are not implicitly
--- allowed for that type unless the  package also explicitly defines
--- "Assign" and "Read-From" procedures

Jose' D.

orville@weyrich.UUCP (Orville R. Weyrich) (06/01/91)

In article <9105311025.aa04299@PARIS.ICS.UCI.EDU> Jose Aleman Duarte <jduarte@liege.ICS.UCI.EDU> writes:
>> Is there some way to define a type such that all variables of that
>> type can be only read or only written?  For instance, I have a type that
>> defines the status register of some peripheral which can only be
>> read.  It would be best if the compiler could flag any assignments
>> to variables of such a type as errors.
>
>> Is there some other means for commonly handling this problem?  The
>> compiler being used is VADSWorks, if an implementation-defined solution
>> is required.
>
>Alternatively, you can define a "type" and then define a constant variable
>of that type for read only variables...
>
>subtype XYZ is INTEGER;
>NY_REG: constant XYZ := 0; -- define a  variable that can only be read
>

NO NO NO!

The original poster wants to describe a memory location that can be modified
by an entity outside the control of the program. In C (and many DEC language
extensions) this would be a VOLATILE memory location. If you tell the compiler
that it is constant, the optimizer may copy the value into a register and use
the copy, or do constant folding and substitute the value 0 for the memory
reference.

Can you say BUG?  :-)



--------------------------------------           ******************************
Orville R. Weyrich, Jr., Ph.D.                   Certified Systems Professional
Internet: orville%weyrich@uunet.uu.net             Weyrich Computer Consulting
Voice:    (602) 391-0821                         POB 5782, Scottsdale, AZ 85261
Fax:      (602) 391-0023                              (Yes! I'm available)
--------------------------------------           ******************************

rharwood@east.pima.edu (06/02/91)

In article <3949@titan.tsd.arlut.utexas.edu>,
gardner@titan.tsd.arlut.utexas.edu (Don Gardner) writes:

> Is there some way to define a type such that all variables of that
> type can be only read or only written?  For instance, I have a type that
> defines the status register of some peripheral which can only be 
> read.  It would be best if the compiler could flag any assignments 
> to variables of such a type as errors.
> 
> Is there some other means for commonly handling this problem?  The 
> compiler being used is VADSWorks, if an implementation-defined solution
> is required.

Well, it's been 2.5 years since I did this, but IMHO "the right way" to do what
you are trying is to encapsulate the READ operation in a FUNCTION and the WRITE
operation in a one-parameter procedure.  Several replies to your message
correctly stated that you should "hide the operations in a package," but didn't
show how to implement the desired functionality.

Naturally, you'd have to change/define types as appropriate, but here's a
sample of how I've done it (from memory... don't expect perfect recall!).

(Before anyone attacks the "obvious inefficiencies" here, try inserting 
"PRAGMA INLINE"s for the subprograms and COMPILE it on YOUR compiler, then look
at generated assembler code when you CALL them.  I believe the compiler we had
did almost exactly what the assembler programmer would have done: a direct read
from the IO location.)  Obviously, you should check the performance issues for
YOUR compiler!  This is a bit long... but I hope it's helpful.
============================ package spec
Package My_Device_Controller is

  --
  --  Your "read-only" register interface:
  --

  -- Define the possible status returned as enumerated:
  Type Status is (Device_Busy, Device_Idle, Device_Error);  -- etc....

  -- And attach the appropriate "real" values:
  For Status use (Device_Busy  => 16#01#,   -- Define as appropriate
                  Device_Idle  => 16#08#,   -- for YOUR controller
                  Device_Error => 16#F0#);  --

  -- Here's the function which provides a "read-only" status value...
  -- See implementation in package body below.
  Function device_status return status;

  --
  -- Now, the "write-only" register interface:
  --

  -- Define the possible control values, also enumerated:
  -- (I'm assuming "read" and "write" commands are concerned with reading/
  -- writing device DATA, not CONTROL information.)
  Type controls is (Write_Command, Read_Command,     -- Etc.... add others
                    Status_Command, Reset_Command);  -- if needed

  -- Define the "real values":
  For Controls use (Write_Command  => 16#01#,  -- Again, use whatever
                    Read_Command   => 16#04#,  -- your hardware dudes
                    Status_Command => 16#14#,  -- give you (normally
                    Reset_Command  => 16#F9#); -- in HEX!)

  Procedure Device_Command ( command : in controls );
end My_Device_Controller;
============================ package body
With Low_Level_IO;  
    -- or maybe you use Memory-Mapped... that's a bit trickier,
    -- but I've done that too. (Not here, for brevity!)
Package Body My_Device_Controller is

  -- The LRM 14.6 defines Low_Level_IO, but does not explicitly
  -- define type "device_type"; an exercise for the interested...
  Status_Register  : constant Low_level_IO.Device_type := 16#F001#;
  Control_Register : constant Low_level_IO.Device_type := 16#F003#;

  Function device_status return status is
    results : Low_Level_IO.data_type;
  begin
    Low_Level_IO.Receive_Control(Device=> Status_Register, Data => results);
    Return Results;  -- You may need some UNCHECKED_CONVERSION here...
  end device_status;

  Procedure Device_Command ( command : in controls ) is
  begin
    -- Again, you may need a little CONVERSION...
    Low_Level_IO.Send_Control(Device=> Control_Register, Data => Command);
  end Device_Command;
end My_Device_Controller;
============================ code usage fragment
With My_Device_Controller; Use My_Device_Controller;
..
..
  Device_Command (Status_command);  -- tell the device to report!
  Case device_Status is
    When Device_idle  => process_whatever_you_want;
    When Device_busy  => dont_do_anything_now;
    When Device_error => Device_Command(reset_command); -- or whatever!
  end case;
..
..
  Device_Command(Write_command);
  Device_Command(Read_command);
..

orville@weyrich.UUCP (Orville R. Weyrich) (06/03/91)

In article <1991Jun2.095459.1@east.pima.edu> rharwood@east.pima.edu writes:
>In article <3949@titan.tsd.arlut.utexas.edu>,
>gardner@titan.tsd.arlut.utexas.edu (Don Gardner) writes:
>
>> Is there some way to define a type such that all variables of that
>> type can be only read or only written?  For instance, I have a type that
>> defines the status register of some peripheral which can only be 
>> read.  It would be best if the compiler could flag any assignments 
>> to variables of such a type as errors.
>> 
>> Is there some other means for commonly handling this problem?  The 
>> compiler being used is VADSWorks, if an implementation-defined solution
>> is required.
>
>Well, it's been 2.5 years since I did this, but IMHO "the right way" to do what
>you are trying is to encapsulate the READ operation in a FUNCTION and the WRITE
>operation in a one-parameter procedure.  Several replies to your message


I have one concern about this approach: What happens if the compiler optimizer
is SO CLEVER that it notices a write to a location that is never referenced,
and optimizes away the write? 

Does anyone know of a compiler that is this clever?

In any event, you had best check the machine language generated each time
your compiler is upgraded ...

Is there any issue pending before the 9x committee to address the issue of
declaring certain memory locations as VOLATILE?

--------------------------------------           ******************************
Orville R. Weyrich, Jr., Ph.D.                   Certified Systems Professional
Internet: orville%weyrich@uunet.uu.net             Weyrich Computer Consulting
Voice:    (602) 391-0821                         POB 5782, Scottsdale, AZ 85261
Fax:      (602) 391-0023                              (Yes! I'm available)
--------------------------------------           ******************************

jbg@sei.cmu.edu (John Goodenough) (06/04/91)

In article Re: Types defining h/w r/o or w/o registers of 3 Jun 91 07:57:41
GMT orville@weyrich.UUCP (Orville R. Weyrich) writes: 

>Is there any issue pending before the 9x committee to address the issue of
>declaring certain memory locations as VOLATILE?

The following user need and requirement address this issue

    USER NEED U7.1-A: CONTROL OF SHARED MEMORY

    Requirement R7.1-A(1) -- Control of Shared Memory

If you get access to the Requirements Rationale document and look up
"VOLATILE" in the KWIC index, you will be referred directly to this
requirement.  This document is available electronically on the AJPO host and
on the Ada 9X Bulletin Board -- 1-800-Ada9X25 or 301/459-8939.  Or contact the
Ada 9X Project Office at the address below to receive a hard copy.

	Chris Anderson
	Ada 9X Project Manager
	WL/MNAG
	Eglin AFB, FL 32542-5434
	904/882-8264
	e-mail: anderson@uv4.eglin.af.mil
	FAX: 904/882-2095

John B. Goodenough					Goodenough@sei.cmu.edu
Software Engineering Institute				412-268-6391

als@bohra.cpg.oz.au (Anthony Shipman) (06/04/91)

In article <1991Jun3.075741.1945@weyrich.UUCP>, orville@weyrich.UUCP (Orville R. Weyrich) writes:
................
> 
> Is there any issue pending before the 9x committee to address the issue of
> declaring certain memory locations as VOLATILE?


Wasn't there something in the tasking part of the language for this?  Ada's
multi-tasking model is shared memory between tasks.  I vaguely recall there was
a pragma, or something in the machine-dependant chapter, to declare certain
memory locations as volatile.  Certainly what you have is logically equivalent
to multi-tasking except that one of the tasks is implemented in hardware.

-- 
Anthony Shipman                 "You've got to be taught before it's too late,
Computer Power Group             Before you are six or seven or eight,
19 Cato St., East Hawthorn,      To hate all the people your relatives hate,
Melbourne, Australia             You've got to be carefully taught."  R&H

jbg@sei.cmu.edu (John Goodenough) (06/05/91)

In article Re: Types defining h/w r/o or w/o registers of 4 Jun 91 14:48:21 GMT als@bohra.cpg.oz.au (Anthony Shipman) writes:

>Wasn't there something in the tasking part of the language for this?  Ada's
>multi-tasking model is shared memory between tasks.  I vaguely recall there was
>a pragma, or something in the machine-dependant chapter, to declare certain
>memory locations as volatile.  Certainly what you have is logically equivalent
>to multi-tasking except that one of the tasks is implemented in hardware.

You are talking here about pragma SHARED, which is not suitable for use with
volatile data.  The Ada 9X study report on shared variables (available from
the usual sources) discusses why pragma SHARED doesn't fit the bill.

John B. Goodenough					Goodenough@sei.cmu.edu
Software Engineering Institute				412-268-6391

case@shamash.cdc.com (Steven V. Case) (06/06/91)

> Well, it's been 2.5 years since I did this, but IMHO "the right way" to do 
> what you are trying is to encapsulate the READ operation in a FUNCTION and the
> WRITE operation in a one-parameter procedure.  Several replies to your message
> correctly stated that you should "hide the operations in a package," but 
> didn't show how to implement the desired functionality.

I was wondering when someone would mention this.  I too agree that this is the
prefered approach.  It is the method that I have used on all of the projects
I have worked on which have had a similar requirement.

I have read several papers addressing the use of LOW_LEVEL_IO to also serve
this purpose.  Unfortunately, I have not had the pleasure to use a compiler
which provides LOW_LEVEL_IO support.  I would be interested in hearing from
anyone who has some "real life" experience with LOW_LEVEL_IO, especially if
it was used in an embedded, real-time system.

Using the READ function and WRITE procedure approach that you mention, the
biggest problem that I have run into is not that of inefficient code generated
due to the procedure calls.  As you pointed out, using PRAGMA INLINE has always
been able to take care of it.  The problem I have had is with compilers that
have been too aggressive in their optimizations.  Specifically, most of the
systems I have developed for have memory-mapped I/O.  On these systems (that
I am familiar with) the I/O will only work if a read or write operation is
performed at the proper address with the properly "sized" access.  

In such systems, problems appear when the hardware expects a 32-bit access
and one attempts to perform the I/O with something other than a 32-bit access.
This is a problem that I have had to work around frequently.  Typically I 
will define a record which describes the content of the I/O register to be
accessed.  Then if the compiler detects that I am only accessing a portion of
the record, it might attempt to "optimize" the access by using an 8-bit or
a 16-bit access rather than a 32-bit access as required by the hardware.

Since I have been able to deliver these systems, there are obvious ways to
work around the "optimizations" if the compiler demonstrates such behavior.
They just require me to write code that is not quite what I would have 
preferred.  I would also like to hear of others experience with memory 
mapped I/O when LOW_LEVEL_IO support is not present.

Regards,
Steve

-- 
  ____  ____        Steve Case - HQG526           email: case@shamash.cdc.com
 / ___||___ \       Control Data Corporation      AT&T : (612) 853-3345
| |___  ___| |      3101 East 80th Street                 
 \____||____/       Bloomington, MN 55425

rharwood@east.pima.edu (06/09/91)

In article <33884@shamash.cdc.com>, case@shamash.cdc.com (Steven V. Case)
writes:

>      [from a previous post:]
>> Well, it's been 2.5 years since I did this, but IMHO "the right way" to do 
>> what you are trying is to encapsulate the READ operation in a FUNCTION and the
>> WRITE operation in a one-parameter procedure.  Several replies to your message
>> correctly stated that you should "hide the operations in a package," but 
>> didn't show how to implement the desired functionality.

> I was wondering when someone would mention this.  I too agree that this is the
> prefered approach.  It is the method that I have used on all of the projects
> I have worked on which have had a similar requirement.

Thanks for the comments to my posting.  Sometimes I think some folks would
rather argue a philosophical point rather than come to grips with a technical
issue and present a real-world solution which many might learn from!

> I have read several papers addressing the use of LOW_LEVEL_IO to also serve
> this purpose.  Unfortunately, I have not had the pleasure to use a compiler
> which provides LOW_LEVEL_IO support.  I would be interested in hearing from
> anyone who has some "real life" experience with LOW_LEVEL_IO, especially if
> it was used in an embedded, real-time system.

As I mentioned, my "heavy" real-world embedded experience is about 2.5 years
old.  I used to work for a local office of Dalmo Victor, makers of various
radar warning receivers for domestic and foreign Air Forces.  If you have any
specific questions, I'd be glad to address them either via EMail or on the net.

> Using the READ function and WRITE procedure approach that you mention, the
> biggest problem that I have run into is not that of inefficient code generated
> due to the procedure calls.  As you pointed out, using PRAGMA INLINE has always
> been able to take care of it.  The problem I have had is with compilers that
> have been too aggressive in their optimizations.  Specifically, most of the
> systems I have developed for have memory-mapped I/O.  On these systems (that
> I am familiar with) the I/O will only work if a read or write operation is
> performed at the proper address with the properly "sized" access.  

Time has muddied the details, but I think we used a compiler flag to turn off
ALL optimization in the modules which called LOW_LEVEL_IO.  Another possibility
which we explored was using "machine code insertions"; basically you "hand
code" the desired assembler code into an Ada module, which the compiler
faithfully executes, "INLINEs", etc.  Most people would say "Just WRITE the
darn code in an assembler module", but when the contract says "100% Ada
required," you just can't do that.  Some contracts even prohibit machine code
insertions!

> In such systems, problems appear when the hardware expects a 32-bit access
> and one attempts to perform the I/O with something other than a 32-bit access.
> This is a problem that I have had to work around frequently.  Typically I 
> will define a record which describes the content of the I/O register to be
> accessed.  Then if the compiler detects that I am only accessing a portion of
> the record, it might attempt to "optimize" the access by using an 8-bit or
> a 16-bit access rather than a 32-bit access as required by the hardware.

Wouldn't the following code fragment work?

    -- Assume the following definition of the memory-mapped address
    my_io_address : constant system.address := 16#FFE0#;

    --  Assume the following definition of a "thing" AT that address
    --  (i.e., is it 8, 16 or 32 bits of DATA; or could even be a RECORD!)
    my_io_data : memory_mapped_data;
    for my_io_data use at my_io_address;

Now, "read" references to "my_io_data" equate to a memory-mapped read from
address FFE0 hex; same for "writes."  Enclose the read and write in functions,
which are compiled WITHOUT optimization, but WITH pragma inline, that way the
compiler won't optimize:

    my_io_data := 10;
    some_other_thing := my_io_data;
    my_io_data := 12;

into

    some_other_thing := 10;
    my_io_data := 12;

> Since I have been able to deliver these systems, there are obvious ways to
> work around the "optimizations" if the compiler demonstrates such behavior.
> They just require me to write code that is not quite what I would have 
> preferred.  I would also like to hear of others experience with memory 
> mapped I/O when LOW_LEVEL_IO support is not present.

Again, machine code inserts are choice #1.  Another possibility, if your
contract allows and the compiler supports (which most do these days), is to
code an Ada package spec for io_read and io_write, but write an assembler
module for the bodies of the read/write functions.  Then have the compiler
substitute the assembler object module for the expected Ada object module. 
Works great on the VAX, but we had a fit making it work on InterACT's 1750A
compiler.

Ray
-----
Ray Harwood           |Data Basix           |Associate Faculty,    
Voice: (602)721-1988  |PO Box 18324         |   Pima Community College
FAX:   (602)721-7240  |Tucson, AZ 85731     |Instructor in Ada and Pascal
CompuServe: 76645,1370|AppleLink: DATA.BASIX|Internet: rharwood@east.pima.edu