Creating several types from a base type and conversion

346 views
Skip to first unread message

Ken Roberts

unread,
Jan 18, 2020, 2:32:54 AM1/18/20
to
New to ada with a question about types.

The theory is creating an old computer emulator. Memory is an array of BaseWord's (Older computer had 32K of memory, so making an array on a modern computer is peanuts).

Creating a base type that is stored into the memory array

<code>

type BaseWord is array (00 .. 29) of Boolean;
pragma pack (BaseWord);

</code>

Creating derived type(s) that can be returned from the memory section

<code>

type DataWord is new BaseWord with record
Upper : array 00 .. 8#77777# of Boolean;
Lower : array 00 .. 8#77777# of Boolean;
end record;

for DataWord use record
for Upper use 15 .. 29;
for Lower use 00 .. 14;
end record;
pragma pack (DataWord);

type InstructionWord is new BaseWord with record;
(define field parameters)
end record;
for InstructionWord use record
(define BaseWord fields bit locations)
end record;

</code>

Defining how characters for display are packed into memory

<code>
type CharByte is array (0 .. 5) of Boolean; -- Character define
type CharWord is record
CharArray : array (0 .. 4) of CharByte;
end record;
for CharWord use record
for CharArray (0) use 24 .. 29;
for CharArray (1) use 18 .. 23;
for CharArray (2) use 12 .. 17;
for CharArray (3) use 6 .. 11;
for CharArray (4) use 0 .. 5;
end record;

</code>

So the next question is how to convert between CharWord/BaseWord?

For most stuff, memory will be returning either DataWord or InstructionWord for each memory access, but I'm also looking at an easier way to manage characters for text display on the emulated monitors.

Simon Wright

unread,
Jan 18, 2020, 7:16:20 AM1/18/20
to
Ken Roberts <aliso...@gmail.com> writes:

> So the next question is how to convert between CharWord/BaseWord?

Possibly using Ada.Unchecked_Conversion. Hidden inside a function with a
helpful-in-six-months name.

It takes very little time to compile code nowadays, I'd strongly suggest
you start with the basics (e.g. BaseWord) and build up from there,
checking as you go (there are a _lot_ of syntax errors in your example
code).

You can't say

type DataWord is new BaseWord with record
Upper : array 00 .. 8#77777# of Boolean;
Lower : array 00 .. 8#77777# of Boolean;
end record;

because BaseWord isn't a tagged type, and even if it was you don't want
to add extra information, you're looking for an alternative view.
And that's not the syntax for an array declaration.
And you can't declare an anonymous array in a record.
And that's two large arrays (I think you're aiming for representations).

----------

I was wondering whether an Unchecked Union (ARM B.3.3[1]) might serve,
but (30) says "The use of an unchecked union to obtain the effect of an
unchecked conversion results in erroneous execution".

[1] http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-B-3-3.html

Ken Roberts

unread,
Jan 18, 2020, 7:49:17 AM1/18/20
to
On Saturday, January 18, 2020 at 4:16:20 AM UTC-8, Simon Wright wrote:
Don't know about the syntax errors - gnatmake seems to like it. But
then, the example is in a package spec with no body yet. Still working
on the basics first. Just now getting serious about Ada, I'm sure I'll
run into a lot of learning curve on these little details.

The concept is emulating a 30-bit computer from olden days.

It was my understanding that a boolean array would be better than an
integer in order to do some of the bit manipulations that the old
computer was designed for.

One example:

ADD LP : L[Y*(Q)]+(A) -> A

Take the logical product of Y and Q register, then add A register,
place results in A register.

(LP being boolean AND of 2 registers)

I think I tried doing tagged records and subtypes, but kept getting
errors like 'Bits already mapped' when trying to extend the BaseWord
(30 bit) into a data word ( 2 separate 15-bit fields) and instruction
word (5 separate bit-mapped fields) while still being able to easily
convert between BaseWord and others (think pulling next instruction from memory array, then pulling data from arbitrary location in memory array).

Functionally, it would be relatively easy to just ignore the hardware
aspect of the emulation, but I'm trying to set it up so I can emulate
the hardware later as a learning tool to how this old computer
actually did things (like 1's complement subtractive addition). The
real fun will be programming the timing (1MHz clock split into 4
phases, with interesting interrupt handling).

I know - _very_ ambitious project for a beginner in the language (not
to programming), but I figure might as well have an interesting
project to work on while learning rather than the basic "Hello World"
style that seems to be prevalent.

Optikos

unread,
Jan 18, 2020, 9:17:40 AM1/18/20
to
On Saturday, January 18, 2020 at 6:16:20 AM UTC-6, Simon Wright wrote:
The Unchecked_ series are better focused on the situation that BaseWord, DataWord, InstructionWord, and CharWord are originating in another language, say, C, but that is ostensibly not the case here.

More proper in Ada, would be to have multiple conversion functions that take the already-extant FooWord as a parameter and return an extracted/converted BarWord as return value (or a procedure that takes the already-extant FooWord as an in parameter and the to-be-extracted/to-be-converted BarWord as an out parameter). Then that conversion/extraction subroutine is merely performing garden-variety safe/not-unchecked reading of FooWord and is merely performing garden-variety safe/not-unchecked writing of BarWord.

You would have a plethora of such FooWord to BarWord conversion/extraction subroutines—one for each origin-to-destination pairing in your app-domain, such as {ExtractCharWordFromDataWord, EmbedCharWordIntoDataWord, ExtractDataWordFromInstructionWordOpcode, EmbedDataWordIntoInstructionWordOpcode, ExtractDataWordFromInstructionWordImmediate, EmbedDataWordIntoInstructionWordImmediate, …} (assuming that immediates in the machine code are located at the same offset-from-beginning-of-instruction in all machine instructions, otherwise ExtractDataWordFromImmediateForSuchandsuchOpcode and EmbedDataWordIntoImmediateForSuchandsuchOpcode for each different format of suchandsuch opcodes in the instruction-set) and so forth.

In modern Ada, for efficiency and perhaps succinct/readable beauty, you might want to consider the extended return statement if choosing the design above that is based on functions (instead of the out parameter to a procedure, which is available in all eras of Ada, but perhaps naturally leading to slightly more verbose/less-lucid invocations at points of usage, due to being statements instead of expressions).

https://www.adaic.org/resources/add_content/standards/05rm/html/RM-6-5.html#S0170

Bill Findlay

unread,
Jan 18, 2020, 9:56:24 AM1/18/20
to
On 18 Jan 2020, Ken Roberts wrote
(in article<4b0649b3-aed2-44fd...@googlegroups.com>):

> The concept is emulating a 30-bit computer from olden days.
>
> It was my understanding that a boolean array would be better than an
> integer in order to do some of the bit manipulations that the old
> computer was designed for.

Use the type:

mod 2**30

which provides all the bit handling you need.
Override the arithmetic operations with your own 1's complement versions.
Roberto es su tio.

For an Ada 2012 emulator of a 48-bit machine, see:

<http://www.findlayw.plus.com/KDF9/emulation/emulator.html>

--
Bill Findlay


Simon Wright

unread,
Jan 18, 2020, 10:10:00 AM1/18/20
to
Ken Roberts <aliso...@gmail.com> writes:

> Don't know about the syntax errors - gnatmake seems to like it.

Then the code you're compiling isn't the code you're showing to us!

Simon Wright

unread,
Jan 18, 2020, 10:47:04 AM1/18/20
to
Ken Roberts <aliso...@gmail.com> writes:

> I know - _very_ ambitious project for a beginner in the language (not
> to programming), but I figure might as well have an interesting
> project to work on while learning rather than the basic "Hello World"
> style that seems to be prevalent.

Good stuff!

My first processor emulator was in VAX Fortran, later migrated to
C. There were Ada and C++ bindings for adding peripherals as
required. Someone used the C++ bindings & threads to emulate a
CRT/lightpen on an X Windows touchscreen. Occasional threading problems,
surprise :-)

Nowadays I'd start from Ada, obvs

Jeffrey R. Carter

unread,
Jan 18, 2020, 11:13:21 AM1/18/20
to
On 1/18/20 3:56 PM, Bill Findlay wrote:
>
> Use the type:
>
> mod 2**30
>
> which provides all the bit handling you need.

Except for shifts and rotations, which most instruction sets provide. Shifts can
be replaced by multiplication and division by powers of two. Rotations are a
little more complex.

--
Jeff Carter
"He didn't get that nose from playing ping-pong."
Never Give a Sucker an Even Break
110

Niklas Holsti

unread,
Jan 18, 2020, 12:57:08 PM1/18/20
to
On 2020-01-18 9:32, Ken Roberts wrote:
> New to ada with a question about types.

Welcome to the language!

What did you program in before? Sometimes it is easier to give advice if
one knows the background of the person asking for it.

> The theory is creating an old computer emulator. Memory is an array
> of BaseWord's (Older computer had 32K of memory, so making an array
> on a modern computer is peanuts).

Yes, but note that in some Ada compilers, the predefined Integer is only
16 bits, so some memory address computations (offsets) in a 32K range
might overflow... better define your own "memory address" type with the
computational range you need (which may be more than 32K, or should
perhaps use modular arithmetic).

> Creating a base type that is stored into the memory array
>
> <code>
>
> type BaseWord is array (00 .. 29) of Boolean;
> pragma pack (BaseWord);
>
> </code>

Note that such a definition does not ensure that the Boolean with index
0 is the least significant bit, or the most significant bit -- different
compilers can use different conventions. And there is no way in current
Ada to specify the indexing order.

I think the first thing you should consider, and decide, is whether you
want your emulator to be fully portable to all standard Ada
implementations, or to the commonly available Ada compilers. The main
issues are the endianness and the word length of the computer you want
to emulate:

- Can the computer to be emulated address only full 30-bit words, or can
it address also, say, characters (bytes) within a word?

- Is the computer you intend to emulate big-endian or little-endian?
There are two aspects to this question: first, conventions for numbering
bits (is bit 0 most significant or least significant?) and second,
addressing sub-word-units like characters (if possible) within a word
(does the lowest sub-word address access the most significant end of the
addressed word, or the least-significant end?).

I'll sketch an approach that applies to the commonly available Ada
compilers and makes no a-priori assumptions on the endianness of the
computer to be emulated.

I recommend starting from the type Interfaces.Unsigned_32. This is a
32-bit, unsigned, modular integer type, and has predefined shift and
rotate operations as well as bit-wise Boolean operations. The type is
not required to exist in all Ada compilers (for example, the computer
you are emulating would probably instead have had a type
Interfaces.Unsigned_30) but does exist in GNAT and other Ada compilers
for most modern computers which tend to have 32-bit machine integers.

The 30-bit unsigned integer type is then

subtype Base_Word is Interfaces.Unsigned_32 range 0 .. 2**30 - 1;

and the emulated memory is an array of 2**15 Base_Word elements, indexed
by the memory-address type.

Note that Base_Word does not wrap around at 30 bits (as the type "mod
2**30" would) but at 32 bits. But you can emulate 30-bit wrap-around by
clearing the two high bits after every arithmetic operation.

I would then define a function to extract any desired bit-field from a
Base_Word, for example

function Field (Word : Base_Word; Low_Bit, High_Bit : Bit_Number)
return Base_Word

where Bit_Number is range 0 .. 29.

The easiest way to implement such a function is by the left-shift and
right-shift operations for Base_Word. In this implementation, you can
decide if you want Bit_Number to work in a little-endian or big-endian way.

I would also define a procedure Set_Field similarly:

procedure Set_Field (
Word : in out Base_Word;
Low_Bit, High_Bit : in Bit_Number;
New_Value : in Base_Word);

Again, this can be done with the shift operations (or rotate operations)
and some Boolean operations on Base_Word.

If the computer to be emulated can access sub-word data, for example
bytes with a byte address, you can now use the above subprograms to
implement operations to read or write such sub-word data, and again you
can implement either little-endian sub-word order or big-endian sub-word
order.

These tools should let you decode instructions and their bit-fields and
emulate all "logical" (Boolean) instructions and load/store instructions.

If you prefer to implement the bit-field lay-outs of instruction words
by means of record representation clauses, that is fine too. However,
note that the endianness of the bit numbering in such clauses may be
different in different Ada compilers, so you should also use the
Bit_Order aspect, together with the representation clause, to define the
order you want to use (which is probably the same as in the
instruction-set manuals for the emulated computer). Also, you then have
to use Unchecked_Conversion to convert between Base_Word and the
instruction record types, but that is good and safe as long as you have
taken care of the bit order.

For the arithmetic instructions, you must of course first consider how
the emulated computer represents negative numbers: two's complement,
one's complement, or sign-magnitude?

The answer will tell you how to convert, using ordinary type conversions
and ordinary arithmetic (not Unchecked_Conversion) from Base_Word to the
corresponding signed type (for use as an operand in the emulation of an
arithmetic instruction) and from the signed type (as the result of the
arithmetic instruction) back to Base_Type for storing in an emulated
register or in the emulated memory.

Remember that you may have to use wider types for the results of
arithmetic operations so as to handle and detect overflows. Unsigned_32
already gives you two more high-end bits, so it can be used for addition
and subtraction, but for multiplication you may want to use a 64-bit
type, for example.

(I agree with others that there are some errors in the declarations you
gave (elided here) which should have made GNAT reject the code.)
> For most stuff, memory will be returning either DataWord or
> InstructionWord for each memory access, but I'm also looking at an
> easier way to manage characters for text display on the emulated
> monitors.
I would assume that in the original system, the diplay of characters on
the monitors was implemented in the SW programs that ran on the emulated
processor. Why should the processor emulator do something special for
this? Did the monitors display data directly from "display buffers" in
the 32K memory, using DMA?

--
Niklas Holsti
Tidorum Ltd
niklas holsti tidorum fi
. @ .

Bill Findlay

unread,
Jan 18, 2020, 1:20:42 PM1/18/20
to
On 18 Jan 2020, Jeffrey R. Carter wrote (in
article<qvvaqs$k9p$1...@dont-email.me>):

> On 1/18/20 3:56 PM, Bill Findlay wrote:
> >
> > Use the type:
> >
> > mod 2**30
> >
> > which provides all the bit handling you need.
>
> Except for shifts and rotations, which most instruction sets provide.
> Shifts can be replaced by multiplication and division by powers of two.
> Rotations are a little more complex.

Or, using GNAT, e.g.:

type word is mod 2**30;

subtype word_shift_length is Natural range 0..30;

function shift_word_left (W : word; amount : word_shift_length)
return KDF9.word;

function shift_word_right (W : word; amount : word_shift_length)
return KDF9.word;

function rotate_word_left (W : word; amount : word_shift_length)
return KDF9.word;

function rotate_word_right (W : word; amount : word_shift_length)
return KDF9.word;

All done.

--
Bill Findlay


Jeffrey R. Carter

unread,
Jan 18, 2020, 1:32:51 PM1/18/20
to
On 1/18/20 7:20 PM, Bill Findlay wrote:
>
> Or, using GNAT, e.g.:
>
> type word is mod 2**30;
>
> subtype word_shift_length is Natural range 0..30;
>
> function shift_word_left (W : word; amount : word_shift_length)
> return KDF9.word;
>
> function shift_word_right (W : word; amount : word_shift_length)
> return KDF9.word;
>
> function rotate_word_left (W : word; amount : word_shift_length)
> return KDF9.word;
>
> function rotate_word_right (W : word; amount : word_shift_length)
> return KDF9.word;
>
> All done.

I don't know what KDF9.Word is, but surely you have to implement these. Or are
you saying GNAT magically implements them?

Simon Wright

unread,
Jan 18, 2020, 3:34:12 PM1/18/20
to
"Jeffrey R. Carter" <spam.jrc...@spam.not.acm.org> writes:

> On 1/18/20 7:20 PM, Bill Findlay wrote:

>> type word is mod 2**30;
>>
>> subtype word_shift_length is Natural range 0..30;
>>
>> function shift_word_left (W : word; amount : word_shift_length)
>> return KDF9.word;

> I don't know what KDF9.Word is, but surely you have to implement
> these. Or are you saying GNAT magically implements them?

It doesn't; 'with Import, convention => Intrinsic' requires the first
argument to have size 8, 16, 32, or 64, and the second argument must be
Natural (not a subtype of it, even if unconstrained).

Ken Roberts

unread,
Jan 18, 2020, 5:16:20 PM1/18/20
to
On Saturday, January 18, 2020 at 7:10:00 AM UTC-8, Simon Wright wrote:
> Ken Roberts <snip> writes:
>
> > Don't know about the syntax errors - gnatmake seems to like it.
>
> Then the code you're compiling isn't the code you're showing to us!


ken/ntds/usq20/playground $ gnatmake core
gcc -c core.ads
ken/ntds/usq20/playground $ ls -al
drwxrwxr-x. 2 ken ken 4096 Jan 18 14:10 .
drwxrwxr-x. 8 ken ken 4096 Jan 16 03:34 ..
-rw-rw-r--. 1 ken ken 1201 Jan 16 03:49 core.ads
-rw-rw-r--. 1 ken ken 280 Jan 18 14:10 core.ali
-rw-rw-r--. 1 ken ken 968 Jan 18 14:10 core.o
ken/ntds/usq20/playground $ 

<code>

package core is

--
-- 30-bit word size (CP642/A/B)
-- Data : 0 .. 2**29
-- Half-word
-- Upper : at 0 range 15 .. 29
-- Lower : at 0 range 00 .. 14
-- Instruction word (normal)
-- f : at 0 range 24 .. 29 Op code (instruction)
-- j : at 0 range 21 .. 23 Skip condition
-- k : at 0 range 18 .. 20 Read/Write/Replace type
-- b : at 0 ragne 15 .. 17 Index register
-- y : at 0 range 00 .. 14 Operand/constant
-- Instruction word (I/O) (f => 13 | 17 | 62 | 63 | 66 | 67 | 73 .. 76
-- f : at 0 range 24 .. 29 Op code (instruction)
-- j^ : at 0 range 20 .. 23 I/O channel (C^n)
-- k^ : at 0 range 18 .. 19 Buffer control address modifier
-- b : at 0 range 15 .. 17 Index register
-- y : at 0 range 00 .. 14 Operand/buffer address

WORD_BITS : constant := 30;
WORD_MASK : constant := 8#77777_77777#;
HALF_WORD_BITS : constant := 15;
HALF_WORD_MASK : constant := 8#77777#;
CHAR_BITS : constant := 6;
CHAR_MASK : constant := 8#77#; -- Used for pulling out 6-bit character code from full word

type BaseWord is array (0 .. (WORD_BITS - 1)) of Boolean;

-- Although we use constants here for definition, use hard numbers for
-- record components for now
type DataWord is new BaseWord with record
Upper : array (0 .. (HALF_WORD_BITS - 1)) of Boolean;
Lower : array (0 .. (HALF_WORD_BITS - 1)) of Boolean;
end record;
for DataWord use record
for Upper use HALF_WORD_BITS .. (WORD_BITS - 1);
for Lower use 00 .. (HALF_WORD_BITS - 1);
end record;
pragma pack (DataWord);

-- Type 1 instructions are normal instructions
type Instruction1 is new BaseWord with record
F : array 0 .. 8#77# of Boolean;
J : array 0 .. 8#7# of Boolean;
K : array 0 .. 8#7# of Boolean;
B : array 0 .. 8#7# of Boolean;
Y : array 0 .. 8#77777# of Boolean;
end record;
for Instruction1 use record
for F use 24 .. 29;
for J use 21 .. 23;
for K use 18 .. 20;
for B use 15 .. 17;
for Y use 00 ,, 14;
end record;
pragma pack (Instruction1);

-- Type 2 instructions are I/O related instructions - J/K fields become J^/K^ fields
-- where J^ steals one bit from K^ to specify in/out channel
type Instruction2 is new BaseWord with record
F : array 0 .. 8#77# of Boolean;
J : array 0 .. 8#17# of Boolean;
K : array 0 .. 8#3# of Boolean;
B : array 0 .. 8#7# of Boolean;
Y : array 0 .. 8#77777# of Boolean;
end record;
for Instruction2 use record
for F use 24 .. 29;
for J use 20 .. 23;
for K use 18 .. 19;
for B use 15 .. 17;
for Y use 00 .. 14;
end record;
pragma pack (Instruction2);


type CharByte is array (0 .. 5) of Boolean; -- Character define
type CharWord is record
CharArray : array (0 .. 4) of CharByte;
end record;
for CharWord use record
for CharArray (0) use 24 .. 29;
for CharArray (1) use 18 .. 23;
for CharArray (2) use 12 .. 17;
for CharArray (3) use 6 .. 11;
for CharArray (4) use 0 .. 5;
end record;
pragma pack (CharWord)

end core;

</code>

Ken Roberts

unread,
Jan 18, 2020, 5:20:10 PM1/18/20
to
Thanks for the link - will look into it.

Simon Wright

unread,
Jan 18, 2020, 5:35:52 PM1/18/20
to
Ken Roberts <aliso...@gmail.com> writes:

> On Saturday, January 18, 2020 at 7:10:00 AM UTC-8, Simon Wright wrote:
>> Ken Roberts <snip> writes:
>>
>> > Don't know about the syntax errors - gnatmake seems to like it.
>>
>> Then the code you're compiling isn't the code you're showing to us!
>
>
> ken/ntds/usq20/playground $ gnatmake core
> gcc -c core.ads
> ken/ntds/usq20/playground $ ls -al
> drwxrwxr-x. 2 ken ken 4096 Jan 18 14:10 .
> drwxrwxr-x. 8 ken ken 4096 Jan 16 03:34 ..
> -rw-rw-r--. 1 ken ken 1201 Jan 16 03:49 core.ads
> -rw-rw-r--. 1 ken ken 280 Jan 18 14:10 core.ali
> -rw-rw-r--. 1 ken ken 968 Jan 18 14:10 core.o
> ken/ntds/usq20/playground

Well, the core.ads you showed is 3 times bigger!!!!

lockheed:cla simon$ ls -l core.*
-rw-r--r-- 1 simon staff 3377 18 Jan 22:23 core.ads
lockheed:cla simon$ gnatmake core.ads
gcc -c core.ads
core.ads:34:17: anonymous arrays not allowed as components
core.ads:35:17: anonymous arrays not allowed as components
core.ads:37:28: missing ";"
core.ads:38:09: missing "end record;" for "record" at line 37
core.ads:39:23: invalid representation clause
core.ads:40:05: no "record" for this "end record"
core.ads:45:13: anonymous arrays not allowed as components
core.ads:46:13: anonymous arrays not allowed as components
core.ads:47:13: anonymous arrays not allowed as components
core.ads:48:13: anonymous arrays not allowed as components
core.ads:49:13: anonymous arrays not allowed as components
core.ads:51:32: missing ";"
core.ads:52:09: missing "end record;" for "record" at line 51
core.ads:53:19: invalid representation clause
core.ads:54:19: invalid representation clause
core.ads:55:19: invalid representation clause
core.ads:56:19: invalid representation clause
core.ads:57:05: no "record" for this "end record"
core.ads:63:13: anonymous arrays not allowed as components
core.ads:64:13: anonymous arrays not allowed as components
core.ads:65:13: anonymous arrays not allowed as components
core.ads:66:13: anonymous arrays not allowed as components
core.ads:67:13: anonymous arrays not allowed as components
core.ads:69:32: missing ";"
core.ads:70:09: missing "end record;" for "record" at line 69
core.ads:71:19: invalid representation clause
core.ads:72:19: invalid representation clause
core.ads:73:19: invalid representation clause
core.ads:74:19: invalid representation clause
core.ads:75:05: no "record" for this "end record"
core.ads:81:21: anonymous arrays not allowed as components
core.ads:83:28: missing ";"
core.ads:84:09: missing "end record;" for "record" at line 83
core.ads:85:23: missing "use"
core.ads:86:23: missing "use"
core.ads:87:23: missing "use"
core.ads:88:23: missing "use"
core.ads:89:05: no "record" for this "end record"
core.ads:90:27: missing ";"
gnatmake: "core.ads" compilation error
lockheed:cla simon$

Ken Roberts

unread,
Jan 18, 2020, 5:59:54 PM1/18/20
to
On Saturday, January 18, 2020 at 9:57:08 AM UTC-8, Niklas Holsti wrote:
> On 2020-01-18 9:32, Ken Roberts wrote:
> > New to ada with a question about types.
>
> Welcome to the language!
>
> What did you program in before? Sometimes it is easier to give advice if
> one knows the background of the person asking for it.

I touched on C way back when (sometime in the 80's) but my day job was not programming and didn't really dig into it.

For the last few years I've been programming Python (again, hobby, not day job) working on a PJLink control for network-connected projectors in an open source church project.

> > The theory is creating an old computer emulator. Memory is an array
> > of BaseWord's (Older computer had 32K of memory, so making an array
> > on a modern computer is peanuts).
>
> Yes, but note that in some Ada compilers, the predefined Integer is only
> 16 bits, so some memory address computations (offsets) in a 32K range
> might overflow... better define your own "memory address" type with the
> computational range you need (which may be more than 32K, or should
> perhaps use modular arithmetic).
>
> > Creating a base type that is stored into the memory array
> >
> > <code>
> >
> > type BaseWord is array (00 .. 29) of Boolean;
> > pragma pack (BaseWord);
> >
> > </code>
>
> Note that such a definition does not ensure that the Boolean with index
> 0 is the least significant bit, or the most significant bit -- different
> compilers can use different conventions. And there is no way in current
> Ada to specify the indexing order.
>
> I think the first thing you should consider, and decide, is whether you
> want your emulator to be fully portable to all standard Ada
> implementations, or to the commonly available Ada compilers. The main
> issues are the endianness and the word length of the computer you want
> to emulate:
>
> - Can the computer to be emulated address only full 30-bit words, or can
> it address also, say, characters (bytes) within a word?

The computer is 30-bit and the memory is 30-bit magnetic core. The memory control can return either a 30-bit word or 15-bit half-word from upper or lower half of memory address depending on one of the instruction modifiers.

>
> - Is the computer you intend to emulate big-endian or little-endian?
> There are two aspects to this question: first, conventions for numbering
> bits (is bit 0 most significant or least significant?) and second,
> addressing sub-word-units like characters (if possible) within a word
> (does the lowest sub-word address access the most significant end of the
> addressed word, or the least-significant end?).

The memory/computer model is 30-bit words both in computer and memory. No endianness.

32K of directly addressable internal memory. Depending on a modifier field in the instruction word, either a 30-bit word or 15-bit half word (from either upper 15 bits or lower 15 bits) was returned from memory for operatnds.

The addition of 144K of extended memory was provided by an external memory unit approximately the same physical size, and the memory control chassis on the computer was modified at a later date to allow access. Unfortunately, I can't find the documentation on how the external memory was accessed, so I'll have to guess until I can find out.

The computer was actually built before semiconductors became the norm, so the whole computer was built using discreet components on 15-pin circuit cards.
As noted above, this was before semiconductors and bytes became a thing.

The only memory operations were 30-bit word and upper/lower 15-bit half-word get/put operations.

Program accessible registers were 30-bit (with one exception - some operations treated 2 registers as one 60-bit register, but that was for a few selected math functions).

> These tools should let you decode instructions and their bit-fields and
> emulate all "logical" (Boolean) instructions and load/store instructions.
>
> If you prefer to implement the bit-field lay-outs of instruction words
> by means of record representation clauses, that is fine too. However,
> note that the endianness of the bit numbering in such clauses may be
> different in different Ada compilers, so you should also use the
> Bit_Order aspect, together with the representation clause, to define the
> order you want to use (which is probably the same as in the
> instruction-set manuals for the emulated computer). Also, you then have
> to use Unchecked_Conversion to convert between Base_Word and the
> instruction record types, but that is good and safe as long as you have
> taken care of the bit order.
>

See reply to Simon about my program compile operations - it included the field breakdown notes.

> For the arithmetic instructions, you must of course first consider how
> the emulated computer represents negative numbers: two's complement,
> one's complement, or sign-magnitude?
>

The computer is 1's complement subtractive addition, and some instruction modifiers allow for circular shift, shift with sign, and sign extension (for half word operations).


> The answer will tell you how to convert, using ordinary type conversions
> and ordinary arithmetic (not Unchecked_Conversion) from Base_Word to the
> corresponding signed type (for use as an operand in the emulation of an
> arithmetic instruction) and from the signed type (as the result of the
> arithmetic instruction) back to Base_Type for storing in an emulated
> register or in the emulated memory.
>
> Remember that you may have to use wider types for the results of
> arithmetic operations so as to handle and detect overflows. Unsigned_32
> already gives you two more high-end bits, so it can be used for addition
> and subtraction, but for multiplication you may want to use a 64-bit
> type, for example.

True - noted above there are a couple of multiply/divide instructions that treat the 30-bit A and Q registers as one 60-bit register. I was thinking about that and concluded that for those specific instructions it would be better to treat the 60-bit AQ register combination in a private variable since the final operation will still end up being two 30-bit registers.

>
> (I agree with others that there are some errors in the declarations you
> gave (elided here) which should have made GNAT reject the code.)
> > For most stuff, memory will be returning either DataWord or
> > InstructionWord for each memory access, but I'm also looking at an
> > easier way to manage characters for text display on the emulated
> > monitors.
> I would assume that in the original system, the diplay of characters on
> the monitors was implemented in the SW programs that ran on the emulated
> processor. Why should the processor emulator do something special for
> this? Did the monitors display data directly from "display buffers" in
> the 32K memory, using DMA?

Nope. There was no "display" on the computer. The closest thing to a display was a bunch of neon buttons that reflected the contents of the registers on the front panel. The "display" part was either an old-school teletype machine (using 5-bit characters) or a CRT that was a combination radar repeater/computer symbology display unit with attached 15 line X 65 character text display using a really interesting 30-bit layout.

(reaaalllly long URL from google reposted via bit.ly)

http://bit.ly/368sGgl Picture of computer I'm trying to emulate.

Worked on it for 15 years in the Navy.

Ken Roberts

unread,
Jan 18, 2020, 6:03:30 PM1/18/20
to
On Saturday, January 18, 2020 at 2:35:52 PM UTC-8, Simon Wright wrote:
> Ken Roberts <snip> writes:
>
> > On Saturday, January 18, 2020 at 7:10:00 AM UTC-8, Simon Wright wrote:
> >> Ken Roberts <snip> writes:
> >>
> >> > Don't know about the syntax errors - gnatmake seems to like it.
> >>
> >> Then the code you're compiling isn't the code you're showing to us!
> >

<snip>

Interesting. Might be the difference in compilers then - I'm using the ada compiler collection available in the Fedora 30 branch.

Simon Wright

unread,
Jan 18, 2020, 6:38:27 PM1/18/20
to
This is what you posted

> ken/ntds/usq20/playground $ gnatmake core
> gcc -c core.ads
> ken/ntds/usq20/playground $ ls -al
> drwxrwxr-x. 2 ken ken 4096 Jan 18 14:10 .
> drwxrwxr-x. 8 ken ken 4096 Jan 16 03:34 ..
> -rw-rw-r--. 1 ken ken 1201 Jan 16 03:49 core.ads <<<<<< 1201 bytes
> -rw-rw-r--. 1 ken ken 280 Jan 18 14:10 core.ali
> -rw-rw-r--. 1 ken ken 968 Jan 18 14:10 core.o
> ken/ntds/usq20/playground

and this is what I got from copying & pasting the code you'd posted

lockheed:cla simon$ ls -l core.*
-rw-r--r-- 1 simon staff 3377 18 Jan 22:23 core.ads <<<<<< 3377 bytes

I can 100% assure you that the difference in results IS NOT caused by a
difference in compilers: it IS caused by the fact that the code you sent
us is not the same as the code you compiled.

Niklas Holsti

unread,
Jan 18, 2020, 7:07:35 PM1/18/20
to
On 2020-01-19 0:16, Ken Roberts wrote:
> <code>
>
> package core is
>
> --
> -- 30-bit word size (CP642/A/B)
> -- Data : 0 .. 2**29
> -- Half-word
> -- Upper : at 0 range 15 .. 29
> -- Lower : at 0 range 00 .. 14

This means that you (and/or the documentation of the original computer)
are using little-endian bit numbering, in which the less-significant 15
bits are numbered 0 .. 14, and the more significant are numbered 15 .. 29.

Note, again, that the bit-numbering scheme can be different in different
Ada compilers or for different target computers, which means that if you
Unchecked_Convert a record type with a representation clause from or to
an integer or modular type (wuth the same size) you must use the
Bit_Order aspect to ensure portability across Ada compilers.

> WORD_BITS : constant := 30;
> WORD_MASK : constant := 8#77777_77777#;
> HALF_WORD_BITS : constant := 15;
> HALF_WORD_MASK : constant := 8#77777#;

Most Ada programmers do not use all-caps for constants (or named
numbers, as here), because they are not preprocessor macros as in C, but
are more like ordinary objects or variables in Ada. You can of course
define your own style, this is just a remark.

> for DataWord use record
> for Upper use HALF_WORD_BITS .. (WORD_BITS - 1);
> for Lower use 00 .. (HALF_WORD_BITS - 1);
> end record;
> pragma pack (DataWord);

Pragma Pack has two uses: first, to reduce the amount of memory used for
data, which is probably not relevant for your emulator; second, to
squeeze records like this into a "word" of a certain size, usually to
make it possible to Unchecked_Convert it to/from some other type of the
same size, and this is probably your goal here. But note that pragma
Pack, even when accepted by the compiler, does _not_ mean that the
compiler uses a size that is the sum of the widths of the bit-ranges in
the representation clause; the compiler could, for whatever reason, add
some space. Therefore it is good practice, IMO, to always add a Size
clause if you want a certain size. Therefore I would add, to every
pragma Pack that is meant to produce 30 bits, the clause "for
the_type'Size use 30;".

(But the errors in the type declarations must first be corrected, of
course.)

Ken Roberts

unread,
Jan 18, 2020, 7:12:10 PM1/18/20
to
https://drive.google.com/file/d/1jZv-GcGL3PdqYqi60eIYZnRIndudZqic/view?usp=sharing

Here's a google drive link to check out - no changes and no copy/paste

Niklas Holsti

unread,
Jan 18, 2020, 7:30:15 PM1/18/20
to
On 2020-01-19 0:59, Ken Roberts wrote:
> On Saturday, January 18, 2020 at 9:57:08 AM UTC-8, Niklas Holsti wrote:
>> On 2020-01-18 9:32, Ken Roberts wrote:
>>> New to ada with a question about types.
>>
>> Welcome to the language!
>>
>> What did you program in before? Sometimes it is easier to give advice if
>> one knows the background of the person asking for it.
>
> I touched on C way back when (sometime in the 80's) but my day job
> was not programming and didn't really dig into it.
>
> For the last few years I've been programming Python (again, hobby,
> not day job) working on a PJLink control for network-connected
> projectors in an open source church project.

Ok, thanks, this is helpful.

>> - Can the computer to be emulated address only full 30-bit words, or can
>> it address also, say, characters (bytes) within a word?
>
> The computer is 30-bit and the memory is 30-bit magnetic core. The
> memory control can return either a 30-bit word or 15-bit half-word
> from upper or lower half of memory address depending on one of the
> instruction modifiers.

Is it the AN/USQ-20? https://en.wikipedia.org/wiki/AN/USQ-20

>> - Is the computer you intend to emulate big-endian or little-endian?
>> There are two aspects to this question: first, conventions for numbering
>> bits (is bit 0 most significant or least significant?) and second,
>> addressing sub-word-units like characters (if possible) within a word
>> (does the lowest sub-word address access the most significant end of the
>> addressed word, or the least-significant end?).
>
> The memory/computer model is 30-bit words both in computer and
> memory. No endianness.
>
> 32K of directly addressable internal memory. Depending on a modifier
> field in the instruction word, either a 30-bit word or 15-bit half
> word (from either upper 15 bits or lower 15 bits) was returned from
> memory for operatnds.

Ok, so there are no "half-word addresses" in the HW, so no half-word
endianness.

Your other posts suggest that the manuals used little-endian bit
numbering, so I assume you will use the same bit-numbering order in the
record-representation clauses. You should then specify the Bit_Order
aspect as System.Low_Order_First for those record types.

From your other posts, it seems the practice was to store five 6-bit
characters in a 30-bit word. There must be some "endianness" convention
here: is the first of these five characters stored in the high bits of
the word, or in the low bits? If the HW does not support character
addressing, this endianness convention was defined and implemented by
the SW running on the computer, and so does not have to be considered in
the emulator per se.

>>> For most stuff, memory will be returning either DataWord or
>>> InstructionWord for each memory access, but I'm also looking at an
>>> easier way to manage characters for text display on the emulated
>>> monitors.
>> I would assume that in the original system, the diplay of characters on
>> the monitors was implemented in the SW programs that ran on the emulated
>> processor. Why should the processor emulator do something special for
>> this? Did the monitors display data directly from "display buffers" in
>> the 32K memory, using DMA?

> The "display" part was either an old-school teletype machine (using
> 5-bit characters)

I assume that the SW in the machine was responsible for sending the
characters to the teletype, one by one, so the method for storing
characters in words was a SW convention, right?

> or a CRT that was a combination radar repeater/computer symbology
> display unit with attached 15 line X 65 character text display using
> a really interesting 30-bit layout.
That sounds like possibly involving a HW-defined lay-out of characters
in words. If you emulate this CRT, you may have to implement that in the
emulator.

> (reaaalllly long URL from google reposted via bit.ly)
>
> http://bit.ly/368sGgl Picture of computer I'm trying to emulate.

Looks very much like the AN/USQ-20. And the acronym "NTDS" matches
"Naval Tactical Data System", check.

Ken Roberts

unread,
Jan 18, 2020, 7:33:47 PM1/18/20
to
On Saturday, January 18, 2020 at 3:38:27 PM UTC-8, Simon Wright wrote:

<snip>

Anyway - the posted code is an example of what I'm trying to start with for building the rest. ATM, this is just an example and for purpose of this discussion I'm just trying to figure out the basics of:

- Basic type to be able to easily put common 30-bit value into memory
- Basic type that when getting from memory, can easily be converted to a data word/instruction word/packed character set

Note: the packed character set as described is the way the computer was originally programmed since the output would have to be converted to either:
- send text to 5-bit teletype
- Send text to PPI (plan position indicator - aka radar repeater) (30-bit word)
- Send text to auxiliary CRT (15 lines X 65 character text only) (30-bit word)

The PPI and auxiliary CRT both actually use the same channel - the only difference is the 30-bit word format.

Ken Roberts

unread,
Jan 18, 2020, 8:07:29 PM1/18/20
to
On Saturday, January 18, 2020 at 4:30:15 PM UTC-8, Niklas Holsti wrote:

> Is it the AN/USQ-20? https://en.wikipedia.org/wiki/AN/USQ-20

Yep. although the systems I worked on were the 642A/B variants - the USQ-20 link posted showed the original 642 as designed by Sperry/Univac for the evaluation phase of the NTDS project.


> Ok, so there are no "half-word addresses" in the HW, so no half-word
> endianness.
>

The half-word addressing was to/from memory only - the actual hardware memory controller had an S register (30-bit) that was used for actual memory in/out transfers between memory and the rest of the machine, with Zu and Zl (both 15-bit) registers that would then be taken from/placed into the S register for the actual memory transfer.

> Your other posts suggest that the manuals used little-endian bit
> numbering, so I assume you will use the same bit-numbering order in the
> record-representation clauses. You should then specify the Bit_Order
> aspect as System.Low_Order_First for those record types.
>
> From your other posts, it seems the practice was to store five 6-bit
> characters in a 30-bit word. There must be some "endianness" convention
> here: is the first of these five characters stored in the high bits of
> the word, or in the low bits? If the HW does not support character
> addressing, this endianness convention was defined and implemented by
> the SW running on the computer, and so does not have to be considered in
> the emulator per se.
>

The original program spec was to use CMS1 programming language later updated to CMS2 language. I didn't have access to the CMS compiler or even a system that could compile, so I worked directly with the 642 via the teletype.

I actually had to dig into the microfiche (it was rather nicely laid out - 1 microfiche slide per module, each formatted with CMS code immediately followed by the 642 machine code). there was a separate routine that was invoked to pull the characters out of the machine word and format for the intended device.

IIRC - the routine basically did:

- RJump to unpack routine
- Read next address into A
- Loop until end -- Word loop
- loop 5 -- Character loop
- Circular left shift A 6
- Logical AND A & 8#77#
- Convert A to appropriate character for output device
- Shift A to place character into correct bit position for output device
- Masked OR with A and output buffer word being assembled
- Store A to buffer
- end loop -- Character
- Read next address & check for end of output
- End loop -- Word
- Return jump to (RJump + length of output) for next instruction

Note the output could either be

- 5-bits (1 character) per word for teletype
- 30-bits (1 character plus position location bits) per word for PPI
- 30-bits (3 character) per word for ACRO (Auxiliary Crt Read Out)

> >>> For most stuff, memory will be returning either DataWord or
> >>> InstructionWord for each memory access, but I'm also looking at an
> >>> easier way to manage characters for text display on the emulated
> >>> monitors.
> >> I would assume that in the original system, the diplay of characters on
> >> the monitors was implemented in the SW programs that ran on the emulated
> >> processor. Why should the processor emulator do something special for
> >> this? Did the monitors display data directly from "display buffers" in
> >> the 32K memory, using DMA?
>
> > The "display" part was either an old-school teletype machine (using
> > 5-bit characters)
>
> I assume that the SW in the machine was responsible for sending the
> characters to the teletype, one by one, so the method for storing
> characters in words was a SW convention, right?
>

Correct - see above for the approximate routine that was used. The formatted output was placed into an output buffer, then output was initiated with I/O instruction that used the output buffer start/end memory addresses.

The NTDS computer used a memory address for buffer control that was initiated with an I/O instruction and formatted as:

(I/O base address + channel number) : Mu == current output address, Ml == final output address. When memory detected Mu == Ml, then depending on I/O instruction used, would terminate the buffer, or terminate buffer and generate an interrupt to the computer.

I/O was handled asynchronously and the memory controller had interrupt priority for what section of the computer received the next memory access. The hardware was rather interesting in how memory was setup and controlled - it appears to have a lot more control than the current memory schemes, but with core memory (mini rings of magnets with wires running through them), timing and control was rather interesting.


> > or a CRT that was a combination radar repeater/computer symbology
> > display unit with attached 15 line X 65 character text display using
> > a really interesting 30-bit layout.
> That sounds like possibly involving a HW-defined lay-out of characters
> in words. If you emulate this CRT, you may have to implement that in the
> emulator.
>
> > (reaaalllly long URL from google reposted via bit.ly)
> >
> > http://bit.ly/368sGgl Picture of computer I'm trying to emulate.
>
> Looks very much like the AN/USQ-20. And the acronym "NTDS" matches
> "Naval Tactical Data System", check.

Yep.

There was also a secondary 18-bit computer in use that the 642 talked to, but that's a different project.

Ken Roberts

unread,
Jan 18, 2020, 10:37:37 PM1/18/20
to
If anyone is interested - here's the gitlab link for what I'm working on

https://gitlab.com/ada5/usq20/

Mainly has the documentation I've been looking at and some markdown files I've added.

NOTE: it does not include playground/ directory where previous code examples reside since that's my personal playground to test Ada concepts.

Also - some of the code examples that might be in the src/ directory I know do not work at this time - still playing.

Simon Wright

unread,
Jan 19, 2020, 4:37:21 AM1/19/20
to
Great, so that's a 31-line file, whereas the source you posted has 92
lines.

https://groups.google.com/d/msg/comp.lang.ada/2gqi1zxDk4A/pUO5HBQgCQAJ

I took "here is the code I'm working on" to mean "here is the code I'm
working on" rather than "this is a sketch of the sort of thing I'd like
to do, haven't tried to compile it yet".

Anyway, you've got lots of good hints from others here, so I'll shut up,
except for saying that I'd probably go with Bill Findlay's view:
representation clauses where endianness is involved are a lot more
confusing than shifts and masks.

AdaMagica

unread,
Jan 19, 2020, 6:48:20 AM1/19/20
to
Am Sonntag, 19. Januar 2020 10:37:21 UTC+1 schrieb Simon Wright:
> representation clauses where endianness is involved are a lot more
> confusing than shifts and masks.

See https://en.wikibooks.org/wiki/Ada_Programming/Attributes/%27Bit_Order

This is a complete description of how far we can get with endian independence in current Ada. Ada 202x won't change anything in this respect.

CKWG

Simon Wright

unread,
Jan 19, 2020, 9:51:39 AM1/19/20
to
The description is still, I feel, confusing.

That said, I found this incantation (for an SNTP packet, which is
transmitted in network byte order) works a treat on this LE machine. I
don't have a BE machine to check it on (but the original code was used
on both BE and LE machines).

end record
with
Bit_Order => System.High_Order_First,
Scalar_Storage_Order => System.High_Order_First,
Size => 48 * 8;

Niklas Holsti

unread,
Jan 19, 2020, 10:24:30 AM1/19/20
to
On 2020-01-19 16:51, Simon Wright wrote:
> AdaMagica <christ-u...@t-online.de> writes:
>
>> Am Sonntag, 19. Januar 2020 10:37:21 UTC+1 schrieb Simon Wright:
>>> representation clauses where endianness is involved are a lot more
>>> confusing than shifts and masks.

In my experience, record-representation clauses with non-default
Bit_Order work well as long as you don't exceed the size of the largest
machine scalar (so 32 bits are ok for most systems, 64 bits for some)
and as long as you don't use record components whose types are
themselves records with representation clauses (if Bit_Order is not the
default, GNAT insists on widening the component types to the nearest
larger-or-equal machine scalar, so a 5-bit record, say, is widened to an
8-bit record and GNAT then insists on using at least 8 bits for any
component of that type, which usually breaks the lay-out of the
higher-level record).

>> See https://en.wikibooks.org/wiki/Ada_Programming/Attributes/%27Bit_Order
>>
>> This is a complete description of how far we can get with endian
>> independence in current Ada. Ada 202x won't change anything in this
>> respect.
>
> The description is still, I feel, confusing.
>
> That said, I found this incantation (for an SNTP packet, which is
> transmitted in network byte order) works a treat on this LE machine. I
> don't have a BE machine to check it on (but the original code was used
> on both BE and LE machines).
>
> end record
> with
> Bit_Order => System.High_Order_First,
> Scalar_Storage_Order => System.High_Order_First,

Note that Scalar_Storage_Order is GNAT-specific and not Ada standard.

> Size => 48 * 8;
>

Optikos

unread,
Jan 19, 2020, 11:11:05 AM1/19/20
to
No, it is not entirely complete—not entirely capturing the full industrial practice on this little-endian/big-endian topic. A complete treatment of the topic would include utilizing the build system to swap in either of 2 child packages containing same-named identifiers, such as my aforementioned {ExtractCharWordFromDataWord, EmbedCharWordIntoDataWord, ExtractDataWordFromInstructionWordOpcode, EmbedDataWordIntoInstructionWordOpcode, ExtractDataWordFromInstructionWordImmediate, EmbedDataWordIntoInstructionWordImmediate, ExtractDataWordFromImmediateForSuchandsuchOpcode, EmbedDataWordIntoImmediateForSuchandsuchOpcode} where
1) one child package has the •little-endian• shifting & masking (via either declarative/representation-clauses or bit-twiddling imperative/OR-&-AND-expressions-&-assignments) variant for each of those identifier names
and
2) the other child package has the •big-endian• shifting & masking (…) variant for each of those identifier names.

Yes, I realize that the build system is (unfortunately!) not standardized in Ada, but (nearly?) all Ada compilers since time began have permitted some technique of choosing which packages to include or exclude from the build so that the linker choses this variant or that variant of same-named identifiers to actually be linked into the executable or dynamically-linked library, preferably on a per-ISA basis or on a conditional-compilation basis.

Bill Findlay

unread,
Jan 20, 2020, 11:38:14 AM1/20/20
to
On 18 Jan 2020, Simon Wright wrote
(in article <lyftgcy...@pushface.org>):
D'oh! (Looks around for lost marbles - they are here somewhere.)

Here are extracts from theactual code for KDF9's 48-bit shift operations,
showing how they are built up from the intrinsic 64-bit shifts.
Converting this to 30-bit shifts using 32-bit intrinsics should be simple:

--
-- The fundamental emulated storage unit is the 48-bit word.
--

type word is mod 2**48;

--
-- This is the emulation host's 64-bit unsigned integer type.
--

type u_64 is mod 2**64 with Size => 64;

word_mask : constant := 8#7777777777777777#;

function as_word (u : u_64)
return word
is (word(u and word_mask));

-- This GNAT-specific pragma declares all the 64-bit shift operations.

pragma Provide_Shift_Operators (u_64);

--
-- These are the 48-bit primitive, fixed-direction, shift operations.
--

subtype word_shift_length is Natural range 0..48;

function shift_word_left (W : word; amount : word_shift_length)
return word
is (as_word(shift_left(u_64(W), amount)));

function shift_word_right (W : word; amount : word_shift_length)
return word
is (word(shift_right(u_64(W), amount)));

function rotate_word_left (W : word; amount : word_shift_length)
return word
is (shift_word_left(W, amount) or shift_word_right(W, 48-amount));

function rotate_word_right (W : word; amount : word_shift_length)
return word
is (shift_word_right(W, amount) or shift_word_left(W, 48-amount));

--
-- KDF9 shift operations actually had a signed shift amount; e.g.:
--

function shift_logical (W : word; L : signed_Q_part)
return word
is
(
if abs L > 47 then 0
elsif L < 0 then shift_word_right(W, Natural(-L))
else shift_word_left (W, Natural(+L))
);

... etc ...

Sorry for the temporary brain malfunction!

--
Bill Findlay

Shark8

unread,
Jan 21, 2020, 4:35:24 PM1/21/20
to
On Saturday, January 18, 2020 at 5:49:17 AM UTC-7, Ken Roberts wrote:
>
> The concept is emulating a 30-bit computer from olden days.
Then allow me to suggest you do that, instead:

Type Word is range 0..2**30-1
with Size => 30;

this allows you to also do something like this:
Function Convert( Data : Integer ) return Word
with Inline, Pre => Data in Integer(Word'First)..Integer(Word'Last);
Function Convert( Data : Word ) return Integer
with Inline;
-- ...
Function Convert( Data : Integer ) return Word is ( Word(Data) );
Function Convert( Data : Word ) return Integer is ( Integer(Data) );

>
> It was my understanding that a boolean array would be better than an
> integer in order to do some of the bit manipulations that the old
> computer was designed for.
That might be true; though If you're going that route you may want to number the array indices 1..30.

>
> One example:
>
> ADD LP : L[Y*(Q)]+(A) -> A
>
> Take the logical product of Y and Q register, then add A register,
> place results in A register.
>
> (LP being boolean AND of 2 registers)
>
> I think I tried doing tagged records and subtypes, but kept getting
> errors like 'Bits already mapped' when trying to extend the BaseWord
> (30 bit) into a data word ( 2 separate 15-bit fields) and instruction
> word (5 separate bit-mapped fields) while still being able to easily
> convert between BaseWord and others (think pulling next instruction from memory array, then pulling data from arbitrary location in memory array).
Hm, you *could* make the programs a stream and use the stream read/write attributes. -- Though this *might* be a bit of a bear to debug, it does have the advantage that you could "dump the bits to the stream" and let the Read attribute/function take care of interpreting the data.

>
> Functionally, it would be relatively easy to just ignore the hardware
> aspect of the emulation, but I'm trying to set it up so I can emulate
> the hardware later as a learning tool to how this old computer
> actually did things (like 1's complement subtractive addition). The
> real fun will be programming the timing (1MHz clock split into 4
> phases, with interesting interrupt handling).
Hm, this is interesting... it sounds like it would make a really good Ada/VHDL combined-language project. (But I have no idea how hard that would be compared to just simulating things pure-Ada.)

>
> I know - _very_ ambitious project for a beginner in the language (not
> to programming), but I figure might as well have an interesting
> project to work on while learning rather than the basic "Hello World"
> style that seems to be prevalent.
Yes. I would tend to agree.
Allow me to suggest that you could leverage the power of enumerations for the instruction-set.

Then you could build your instruction-stream off that; eg:
-- Ultra-simplified:
Type OP_CODE is (ADD, DIV, EXMPL); -- SUB, MUL, WTVR.

Type Register_Code is (R1, R2, AD, SB)
with Size => 4; -- Four bit register-code; single-trace active.
For Register_Code use (
R1 => 2#0001#,
R2 => 2#0010#,
AD => 2#0100#,
SB => 2#1000#
);

Type Instruction( Op : Opcode ) is record
case Op is
When ADD => Val_1, Val_2 : Word; -- two Word-size operands.
When DIV => Val_1, Val_2 : Word;
When EXMPL => Register : Register_Code;
-- other operations
end case;
end record
with Static_Predicate => -- NOTE: YOU CAN ENFORCE INSTRUCTION VALIDITY.
(Case Instruction.Op is
When Add => True, -- All values are good.
When Div => Instruction.Val_2 /= 0 or else raise Program_Error with "Invalid bitstream",
When EXMPL => Instruction.Register /= AD
);

The above is a simplified version of something I played around with a few years ago: https://github.com/OneWingedShark/Lamman/blob/master/src/ghost_cpu.ads

IOW, don't be afraid to logically model things a bit more than the bit-twiddling would suggest. Also Bill Findlay's KDF9 emulator is probably a really good resource in using Ada to emulate old hardware. (I think there was an Ada implementation of the WWII Enigma machine or something similar someone built, but I can't remember *who*.)

Niklas Holsti

unread,
Jan 21, 2020, 6:06:55 PM1/21/20
to
On 2020-01-21 23:35, Shark8 wrote:
> On Saturday, January 18, 2020 at 5:49:17 AM UTC-7, Ken Roberts wrote:
>>
>> The concept is emulating a 30-bit computer from olden days.
> Then allow me to suggest you do that, instead:
>
> Type Word is range 0..2**30-1
> with Size => 30;
>
> this allows you to also do something like this:
> Function Convert( Data : Integer ) return Word
> with Inline, Pre => Data in Integer(Word'First)..Integer(Word'Last);

This won't work in Janus/Ada or other Ada compilers (if there are
any...) with a 16-bit Integer. Better use a user-defined type with a
sufficient range.

>> It was my understanding that a boolean array would be better than an
>> integer in order to do some of the bit manipulations that the old
>> computer was designed for.

> That might be true; though If you're going that route you may want to
> number the array indices 1..30.

Why? Bit numbers normally start at zero, in either bit-number order, and
in all computer manuals and instruction sets I have ever seen.

(The rest of my comments are addressed mainly to Ken Roberts.)

>> One example:
>>
>> ADD LP : L[Y*(Q)]+(A) -> A
>>
>> Take the logical product of Y and Q register, then add A register,
>> place results in A register.
>>
>> (LP being boolean AND of 2 registers)

Modular types, including Interfaces.Unsigned_32, have built-in bit-wise
boolean operators, so there is little advantage in using arrays of
booleans instead of modular types.

>> I think I tried doing tagged records and subtypes, but kept getting
>> errors like 'Bits already mapped' when trying to extend the BaseWord
>> (30 bit) into a data word ( 2 separate 15-bit fields) and instruction
>> word (5 separate bit-mapped fields) while still being able to easily
>> convert between BaseWord and others (think pulling next instruction
>> from memory array, then pulling data from arbitrary location in
>> memory array).

Perhaps you (Ken) did not understand that when you extend a tagged
record type, you are *adding* components to the components in the parent
type. The parent type's components are still there, and still occupy the
bit ranges specified in the representation clause for the parent type.
This is not a "remapping" or "reinterpretation" of the bits of the
parent type -- that is what the Unchecked_Conversion function is for.

> Hm, you *could* make the programs a stream and use the stream > read/write attributes. -- Though this *might* be a bit of a
> bear to debug, it does have the advantage that you could "dump > the bits to the stream" and let the Read attribute/function take
> care of interpreting the data.

That's a bad idea, IMO. The emulated RAM should be randomly accessible,
word by word, which is cumbersome and slow (if possible) for streams.

>> Functionally, it would be relatively easy to just ignore the hardware
>> aspect of the emulation, but I'm trying to set it up so I can emulate
>> the hardware later as a learning tool to how this old computer
>> actually did things (like 1's complement subtractive addition). The
>> real fun will be programming the timing (1MHz clock split into 4
>> phases, with interesting interrupt handling).

It seems to me that emulating the instruction semantics (functional
emulation) is a reasonable goal for someone new to Ada. Emulating the
cycle-by-cycle operation of the HW is much more ambitious, especially
when various HW units are running in parallel, and I do not advise
starting there. Better to start from a functional emulation, and tackle
the cycle-accurate emulation later.

Your github documents suggest that you intend use network messages to
emulate data transmitted at clock-rate between the different HW units of
the emulated computer. I suspect that this, together with the
cycle/phase-specific emulation, will not let you reach your target of
running the emulation at the 1 MHz rate of the original computer.

Ken Roberts

unread,
Jan 21, 2020, 8:08:25 PM1/21/20
to
On Tuesday, January 21, 2020 at 3:06:55 PM UTC-8, Niklas Holsti wrote:
> On 2020-01-21 23:35, Shark8 wrote:
> > On Saturday, January 18, 2020 at 5:49:17 AM UTC-7, Ken Roberts wrote:

<snip>

> >> One example:
> >>
> >> ADD LP : L[Y*(Q)]+(A) -> A
> >>
> >> Take the logical product of Y and Q register, then add A register,
> >> place results in A register.
> >>
> >> (LP being boolean AND of 2 registers)
>
> Modular types, including Interfaces.Unsigned_32, have built-in bit-wise
> boolean operators, so there is little advantage in using arrays of
> booleans instead of modular types.
>
> >> I think I tried doing tagged records and subtypes, but kept getting
> >> errors like 'Bits already mapped' when trying to extend the BaseWord
> >> (30 bit) into a data word ( 2 separate 15-bit fields) and instruction
> >> word (5 separate bit-mapped fields) while still being able to easily
> >> convert between BaseWord and others (think pulling next instruction
> >> from memory array, then pulling data from arbitrary location in
> >> memory array).
>
> Perhaps you (Ken) did not understand that when you extend a tagged
> record type, you are *adding* components to the components in the parent
> type. The parent type's components are still there, and still occupy the
> bit ranges specified in the representation clause for the parent type.
> This is not a "remapping" or "reinterpretation" of the bits of the
> parent type -- that is what the Unchecked_Conversion function is for.
>

I kinda understood it was not a remapping but adding components, but was kinda hoping to be able to have:

var.data = 30 bits
var.upper = upper 15 bits of data
var.lower = lower 15 bits of data


> > Hm, you *could* make the programs a stream and use the stream > read/write attributes. -- Though this *might* be a bit of a
> > bear to debug, it does have the advantage that you could "dump > the bits to the stream" and let the Read attribute/function take
> > care of interpreting the data.
>
> That's a bad idea, IMO. The emulated RAM should be randomly accessible,
> word by word, which is cumbersome and slow (if possible) for streams.
>

Correct - I was planning on using

Memory : array (0 .. 32768) of Word;

As I noted, with modern computers this size of array would be pretty much a corner of ram.


> >> Functionally, it would be relatively easy to just ignore the hardware
> >> aspect of the emulation, but I'm trying to set it up so I can emulate
> >> the hardware later as a learning tool to how this old computer
> >> actually did things (like 1's complement subtractive addition). The
> >> real fun will be programming the timing (1MHz clock split into 4
> >> phases, with interesting interrupt handling).
>
> It seems to me that emulating the instruction semantics (functional
> emulation) is a reasonable goal for someone new to Ada. Emulating the
> cycle-by-cycle operation of the HW is much more ambitious, especially
> when various HW units are running in parallel, and I do not advise
> starting there. Better to start from a functional emulation, and tackle
> the cycle-accurate emulation later.
>

The original plan was to emulate the software, but by careful planning should be able to reuse the instruction interpreter functions/procedures when it comes time to actually emulate the hardware - note that the main emulation parts are moving data between registers and bit manipulation within the registers.

> Your github documents suggest that you intend use network messages to
> emulate data transmitted at clock-rate between the different HW units of
> the emulated computer. I suspect that this, together with the
> cycle/phase-specific emulation, will not let you reach your target of
> running the emulation at the 1 MHz rate of the original computer.
>

The network emulation is not for messaging between computer sections - it's for emulating the cabling between the computer and peripheral equipment.

The timing is a consideration both in emulating the software as well as emulating the timing between the peripheral equipment.

The long-term project goal is to emulate the USQ-20 suite - so the message passing via network would simulate the cabling between the nodes. Node examples:

Node 1 : CP642 (unit A)
Node 2 : CP642 (unit B)
Node 3 : CP642 (unit C)
Node 4 : RD231 (Magnetic tape unit)
Node 5 : UGC13 (Teletypewriter)
Node 6 : SYA4 (PPI display - radar repeaters w/symbology)

Simon Wright

unread,
Jan 22, 2020, 3:38:02 AM1/22/20
to
Shark8 <onewing...@gmail.com> writes:

> Type Word is range 0..2**30-1
> with Size => 30;
>
> this allows you to also do something like this:
> Function Convert( Data : Integer ) return Word
> with Inline, Pre => Data in Integer(Word'First)..Integer(Word'Last);

Not sure what advantage this would give over just Word (<integer>)

Ken Roberts

unread,
Jan 22, 2020, 9:18:54 AM1/22/20
to
On Tuesday, January 21, 2020 at 5:08:25 PM UTC-8, Ken Roberts wrote:
> On Tuesday, January 21, 2020 at 3:06:55 PM UTC-8, Niklas Holsti wrote:
> > On 2020-01-21 23:35, Shark8 wrote:
> > > On Saturday, January 18, 2020 at 5:49:17 AM UTC-7, Ken Roberts wrote:

<snip>
> Node 1 : CP642 (unit A)
> Node 2 : CP642 (unit B)
> Node 3 : CP642 (unit C)
> Node 4 : RD231 (Magnetic tape unit)
> Node 5 : UGC13 (Teletypewriter)
> Node 6 : SYA4 (PPI display - radar repeaters w/symbology)

Oops - been a while.

Node 4 : RD243 (Magnetic tape unit)
Node 5 : RD231/UGC13 (Combined paper tape punch-reader/teletypewriter)

And to expand on SYA4:

Computer I/O channel -> RMU (refresh memory unit) -> up to 15 PPI display units

The original SYA4 PPI units could only display symbology as it was sent from the computer. The RMU was added so the computer could load symbols that were to be displayed long term into the RMU, then the RMU did the actual refresh of the symbols without computer intervention.

The upgraded display suite was UYA4 and each console had it's own symbology refresh memory (with a few enhancements) that utilized mostly the same symbology as the SYA4 suite.

Shark8

unread,
Jan 22, 2020, 9:32:07 AM1/22/20
to
On Wednesday, January 22, 2020 at 1:38:02 AM UTC-7, Simon Wright wrote:
SPARK Verification, mostly.

Simon Wright

unread,
Jan 22, 2020, 10:41:01 AM1/22/20
to
Fairly sure that SPARK knows about data ranges.

Optikos

unread,
Jan 23, 2020, 4:39:13 PM1/23/20
to
On Saturday, January 18, 2020 at 1:32:54 AM UTC-6, Ken Roberts wrote:
> New to ada with a question about types.
>
> The theory is creating an old computer emulator. Memory is an array of BaseWord's (Older computer had
> 32K of memory, so making an array on a modern computer is peanuts).
>
> Creating a base type that is stored into the memory array
>
> <code>
>
> type BaseWord is array (00 .. 29) of Boolean;
> pragma pack (BaseWord);
>
> </code>
>
> Creating derived type(s) that can be returned from the memory section
>
> <code>
>
> type DataWord is new BaseWord with record
> Upper : array 00 .. 8#77777# of Boolean;
> Lower : array 00 .. 8#77777# of Boolean;
> end record;
>
> for DataWord use record
> for Upper use 15 .. 29;
> for Lower use 00 .. 14;
> end record;
> pragma pack (DataWord);
>
> type InstructionWord is new BaseWord with record;
> (define field parameters)
> end record;
> for InstructionWord use record
> (define BaseWord fields bit locations)
> end record;
>
> </code>
>
> Defining how characters for display are packed into memory
>
> <code>
> type CharByte is array (0 .. 5) of Boolean; -- Character define
> type CharWord is record
> CharArray : array (0 .. 4) of CharByte;
> end record;
> for CharWord use record
> for CharArray (0) use 24 .. 29;
> for CharArray (1) use 18 .. 23;
> for CharArray (2) use 12 .. 17;
> for CharArray (3) use 6 .. 11;
> for CharArray (4) use 0 .. 5;
> end record;
>
> </code>
>
> So the next question is how to convert between CharWord/BaseWord?
>
> For most stuff, memory will be returning either DataWord or InstructionWord for each memory access,
> but I'm also looking at an easier way to manage characters for text display on the emulated monitors.

Many replies to this posting focused on details of using standard Ada itself or GNAT-specific extensions of Ada itself as the total solution to portability, such as via record-representation clauses (which aren't always portable to Janus/Ada) and/or Scalar_Storage_Order (which is GNAT-only, absent in all other Ada compilers).

Instead of depending on a big-language solution of a very-feature-rich compiler, there is a common industrial practice in both Ada and other languages that I mentioned in my 2 replies: use 2 different packages with same-named identifiers (especially functions or procedures) to link in either the little-endian implementation or big-endian implementation (or any other nonportability that is peculiar to a target ISA or target OS). This solution is at the heart of VIPER and MVVM software architectures for taming the portability beast quite well (going so far as to tame even radical philosophical departures among the very divergent GUIs/window-managers: Gnome/GTK, Windows UWP, Win32, KDE/Qt, Tizen).

The key idea is to utilize the build system on a per-target basis, especially the same build system for all targets (instead of the easier case of a different build system per target OS/ISA/GUI/whatever). The Project Extension portion of gprbuild is how FSF or AdaCore GNAT would link in the little-endian package of same-named identifiers versus the big-endian package of same-named identifiers (or overcoming any other seemingly troublesome lack of portability within the Ada language itself or of your own ossified source code that oops inadvertently/short-sightedly got modeled in the past for only one of the now-needed targets-of-portability). Find a layer of identifiers (especially functions or procedures) that can now be a facade of sorts behind/within which you swap in this versus that nonportable implementation peculiar to each target at link-time (and perhaps just don't even compile the other package that is not needed for the current target-of-interest).

The kind of per-target switcheroo just described above is mentioned in the webpage below:
“… Another use case is a large software system with multiple implementations of a common interface; in Ada terms, multiple versions of a package body for the same spec, or perhaps different versions of a package spec that have the same visible part but different private parts. For example, one package might be safe for use in tasking programs, while another might be used only in sequential applications. …”

https://docs.adacore.com/gprbuild-docs/html/gprbuild_ug/gnat_project_manager.html#project-extension

Ken Roberts

unread,
Jan 24, 2020, 4:35:04 AM1/24/20
to
On Thursday, January 23, 2020 at 1:39:13 PM UTC-8, Optikos wrote:

<snip>

> Instead of depending on a big-language solution of a very-feature-rich compiler, there is a common industrial practice in both Ada and other languages that I mentioned in my 2 replies: use 2 different packages with same-named identifiers (especially functions or procedures) to link in either the little-endian implementation or big-endian implementation (or any other nonportability that is peculiar to a target ISA or target OS). This solution is at the heart of VIPER and MVVM software architectures for taming the portability beast quite well (going so far as to tame even radical philosophical departures among the very divergent GUIs/window-managers: Gnome/GTK, Windows UWP, Win32, KDE/Qt, Tizen).

<snip>

It was my understanding that big/little-endian representations were mainly how data was accessed from memory that used 8-bit bytes for physical memory layout.

Using a 16-bit register as an example:

register: [first byte][second byte]

In big-endian format the memory usage would be:

adx 0: [first byte]
adx 1: [second byte]
adx 2: [first byte]
adx 3: [second byte]

In little-endian format the memory usage would be:

adx 0: [second byte]
adx 1: [first byte]
adx 2: [second byte]
adx 3: [first byte]

Is my understanding off?

AdaMagica

unread,
Jan 24, 2020, 5:04:19 AM1/24/20
to
Am Freitag, 24. Januar 2020 10:35:04 UTC+1 schrieb Ken Roberts:
> It was my understanding that big/little-endian representations were mainly how
> data was accessed from memory that used 8-bit bytes for physical memory layout.
>
> Using a 16-bit register as an example:
>
> register: [first byte][second byte]

Assuming the register is interpreted as a signless whole number, first byte is the MSB, second byte the LSB.
>
> In big-endian format the memory usage would be:
>
> adx 0: [first byte] MSB
> adx 1: [second byte] LSB
> adx 2: [first byte]
> adx 3: [second byte]
>
> In little-endian format the memory usage would be:
>
> adx 0: [second byte] LSB
> adx 1: [first byte] MSB
> adx 2: [second byte]
> adx 3: [first byte]
>
> Is my understanding off?

So it's OK. See
https://en.wikibooks.org/wiki/Ada_Programming/Attributes/%27Bit_Order

Optikos

unread,
Jan 24, 2020, 7:38:53 AM1/24/20
to
Yes, you are correct sort of, depending on how you define “first” and “second”. If first is low-valued powers of 2 increasing ascendingly up to 16 (or 32), then you have it backwards. If first is high-valued powers of 2 decreasing descendingly down to 0, then you have it correct. As AdaMagica also nudged in the better-thinking direction, the terms most-significant byte and least-significant byte and most-significant (16-bit-)word and least-significant word are clearer thinking because they overtly refer to the powers of 2 mathematically. Ada's bit_order refers to whether the ISA refers to the bits (independent of the bytes and words) in power-of-2 order mathematically where 2⁰ is the origin and usually called bit zero (instead of bit one) or opposite of power-of-2 order anti-mathematically/lexicaly where 2¹⁶ is the origin often called bit one (or more rarely bit zero) in 16-bit words and where 2³² is the origin often called bit one (or more rarely bit zero) in 32-bit words.

But I am focusing on a different debate. Should a) an all-knowing omniscient compiler feature be the basis of your portability, deferring the responsibility to the language, or b) overt source code that you write in a structured way be the basis of your portability, taking the responsibility on yourself? The omnsiscent compiler feature needs to present in all compilers across all targets and nonbuggy (or bug-for-bug compatibility) across all targets, whereas the overt source code architecture depends only on having a way to substitute one Ada package for another with same-named identifiers on this target versus that target. In the overt source-code architecture there are little-endian versions of certain functions (or procedures) and big-endian versions of those same-named functions (or procedures). Indeed, the this versus that package switcheroo at build-time can be utilized for more portability challenges than mere endianness, but also for this OS versus that OS (e.g., POSIX versus Windows) and this GUI framework versus that GUI framework (e.g., Gnome/Gtk versus Windows UWP versus Win32 versus Carbon versus Cocoa versus CocoaTouch versus Tizen) all of which is far far far beyond the capabilities of any omniscient compiler to hide. VIPER takes this build-time switcheroo to the Nth degree (but you might not need all of VIPER); MVVM less so than VIPER, but both as a valid stern criticism of MVC's excessive interconnectedness that causes MVC to ruin this degree of portability. All 3 of VIPER, MVVM, and MVC is a criticism of the omniscient compiler feature trying to accomplish the portability for you intrinsically behind the scenes.

Ken Roberts

unread,
Jan 24, 2020, 10:01:57 AM1/24/20
to
On Friday, January 24, 2020 at 4:38:53 AM UTC-8, Optikos wrote:

<snip>

OK - here's what I'm looking at.

Based upon the hardware I worked on (and documentation for the 642 system), apparently it's BE based:

Register representation on panel: R bits 29 .. 0

where MSB is on the left and LSB on the right.

Memory is 30 bits (just like registers) and is physically spread over 5 chassis with 6 bits of each word stored on each chassis:

chassis 1 : bits 5 .. 0
chassis 2 : bits 11 .. 6
chassis 3 : bits 17 .. 12
chassis 4 : bits 23 .. 18
chassis 5 : bits 29 .. 24

If you look up to my third post you can see the basic setup of how a word is interpreted for instructions, with all calculations based on the msb .. lsb concept.

Simon Wright

unread,
Jan 24, 2020, 10:22:10 AM1/24/20
to
Ken Roberts <aliso...@gmail.com> writes:

> Based upon the hardware I worked on (and documentation for the 642
> system), apparently it's BE based:
>
> Register representation on panel: R bits 29 .. 0
>
> where MSB is on the left and LSB on the right.

Looks LE to me.

If the panel shows

bit # ... 5 4 3 2 1 0
lit ... 0 0 0 1 0 1

and the value represented is 5 then it's little-endian; bit 0 is the
least significant bit.

Ken Roberts

unread,
Jan 24, 2020, 10:40:21 AM1/24/20
to
On Friday, January 24, 2020 at 7:22:10 AM UTC-8, Simon Wright wrote:
My concern would be in bit shifting operations, that if a bit is shifted left 20 bits then:

start: 000000000000000000000000000001

end : 000000000100000000000000000000

In one instruction for the 642, it combines two registers to make one 60-bit register for some operations, so representing both 30-bit and 60-bit register operations should be the same (assuming a 32-bit computer that should have both 32-bit and 64-bit operations).

At least that's what will have to be accomplished in the emulation.

As noted earlier, the first pass of what I'm working on is the software emulation, but the long-term goal is to fully emulate the 642 computer using register manipulations rather than just emulating the instructions.

At least for now, the target hardware for compiling the system will probably be a minimum 32-bit hardware (like the current intel 32/64 bit chipsets).

One of the USQ20 system is also an 18-bit computer (CP789) - but I don't plan on emulating that for a while yet (if at all - since the 789 was used to manage the KCMX, which was the interface to the older analog fire control systems and not really needed for what I'm initially targeting the project for).

Simon Wright

unread,
Jan 24, 2020, 10:54:46 AM1/24/20
to
Optikos <ZUERCHER...@outlook.com> writes:

> Instead of depending on a big-language solution of a very-feature-rich
> compiler, there is a common industrial practice in both Ada and other
> languages that I mentioned in my 2 replies: use 2 different packages
> with same-named identifiers (especially functions or procedures) to
> link in either the little-endian implementation or big-endian
> implementation

An alternative approach (which I've now abandoned in favour of the GNAT
Scalar_Storage_Order) was, given e.g.

type SNTP_Timestamp is delta 2.0 ** (-32) range -2.0 ** 31 .. 2.0 ** 31;
for SNTP_Timestamp'Size use 64;

subtype Timestamp_Slice is Ada.Streams.Stream_Element_Array (1 .. 8);

to provide conversion functions like

function To_Timestamp_Slice
(T : SNTP_Timestamp) return Timestamp_Slice is
function Convert
is new Ada.Unchecked_Conversion (SNTP_Timestamp,
Timestamp_Slice);
Tmp : constant Timestamp_Slice := Convert (T);
begin
if Big_Endian then
return Tmp;
else
return (1 => Tmp (8),
2 => Tmp (7),
3 => Tmp (6),
4 => Tmp (5),
5 => Tmp (4),
6 => Tmp (3),
7 => Tmp (2),
8 => Tmp (1));
end if;
end To_Timestamp_Slice;

(Big_Endian was a compile-time constant; GNAT is clever enough not to
generate any object code for the "other" branch).

It got a bit more interesting where there were bit fields involved; the
two branches can declare appropriately-represented derived types and use
type conversion between base and derived type to get the compiler to do
the hard work. See e.g. [1] starting at line 43.

[1] https://sourceforge.net/p/coldframe/adasntp/code/ci/Rel_20070311/tree/SNTP.impl/sntp_support.adb

Ken Roberts

unread,
Jan 25, 2020, 5:37:17 AM1/25/20
to
It might be that I can only really concentrate on learning Ada on my weekends (thursday night -> saturday) subject to $HoneyDoList.

Needless to say, I'm not always the brightest nowadays between age and free time without some extra help, so sorry for seeming a little dense right now.

Here's the computer representation as expressed in documentation and how I'm used to working on the hardware:

30-bit words (both memory storage and hardware registers)

Bit Number : 29 28 .. 01 00
MSBit on left, LSBit on right

30-bit words using half-word (memory access only)

Bit number: 29 28 .. 16 15 14 13 .. 01 00
Half word : | Upper | Lower |

Instruction format:

Bit number: | 29 .. 24 | 23 .. 21 | 20 .. 18 | 17 .. 15 | 14 .. 0 |
Field : | Op code | J | K | B | Y |

Op code : Instruction
j : Skip next instruction modification
k : Source or destination of data
b : Index register to use to modify Y
y : Constant or memory address


Sample program 1 (all numbering is octal notation):

<code>

| Address | Contents | Description
| 00600 | 12130 00601 | f = 12 : Opcode (Load A register)
| | | j = 1 : 1 = always skip next instruction
| | | k = 3 : y is memory address, 30 bits
| | | y = 601 : Data
| 00601 | 00005 00001 | Data
| | | A register initial: 00005 00001
| 00602 | 06000 00017 | f = 06 : Opcode (Shift A left circular)
| | | j = 0 : Execute next instruction
| | | k = 0 : Shift count is y
| | | y = 17 : Data (17 octal, 15 decimal)
| | | A register final: 00001 00005

</code>

Sample program using 60-bit register instruction:

60-bit register
Bit number : | 59 .. 30 | 29 .. 00 |
Registers used : | A reg | Q reg |

<code>

| Address | Contents | Description
| 00600 | 10000 00001 | Load Q register
| | | Q register = 00000 00001
| 00601 | 11000 00005 | Load A register
| | | A register = 00000 00005
| 00602 | 07000 00036 | Left shift AQ register circular
| | | Shift count (36 octal, 30 decimal)
| | |
| | | AQ initial : 00000 00005 00000 00001
| | | A register initial = 00000 00005
| | | Q register initial = 00000 00001
| | |
| | | AQ final : 00000 00001 00000 00005
| | | A register final = 00000 00001
| | | Q register final = 00000 00005

</code>

My programming environment is:

Intel-based system
64-bit Fedora based KDE desktop system
Fedora repository has the GNAT compiler suite (GCC Ada - AdaCore?)

With these considerations in mind, I'm trying to figure out the best way to represent the computer hardware (register and memory).

The register and memory base word would be a 30-bit quantity (Interfaces.unsigned_32 possibly?) with 2**30 bit limit, so all register/memory representations would be easily convertible.

Memory representation will be an array (0 .. #8#77777# or 32,768 words) of 30-bit words.

The 60-bit instructions (there's only 2 shift instructions, multiply, which puts answer in QA register(s)) and divide, which uses combined QA register for data, and puts answer in Q and remainder in A))

Based on Niklas Holsti's recommendation, it looks like the following would be what I want to do:

<code>

package core is

with Interfaces;

subtype Word is Interfaces.Unsigned_32 with range 0 .. 2**30 - 1;

subtype D_Word is Interfaces.Unsigned_64 with record
Ru : Word;
Rl : Word;
end record

for D_Word use record
Ru : at 0 use 59 .. 30;
Rl : at 0 use 29 .. 0;
end record

-- NOTE: I_Word is basically read-only
-- The only use is to make it easier to break down Word into
-- the fields to interpret the instruction and will not change
-- during the instruction execution cycle

I_Word is Word with record
-- Not sure how to define this part yet
-- See below for breakdown of bits-to-fields
end record

for I_Word use record
f : at 0 range 29 .. 24;
j : at 0 range 23 .. 21;
k : at 0 range 20 .. 18;
b : at 0 range 17 .. 15;
y : at 0 range 14 .. 00;
end record

end package core;

</code>

With the above representation, I can use a D_Word for shifting
and be able to ignore unused 32/64 bits in hardware

<code>

-- Circular left shift register
procedure shift_left_logical (R : <> Word'Class,
Count : in Integer) is
private
D : D_Word;

-- NOTE: By putting the same contents into both Ru and Rl
-- I don't have to worry about extra bits from Unsigned_32
D.Ru := R;
D.Rl := R;

-- NOTE: Only bits 5 .. 0 are used, the rest are ignored
-- Add option to limit actual shifting to mod 30 since anything
-- over 30 would just wrap around again
-- ex: Count == 31 is equivalent to Count == 1
-- on a 30-bit register
if Count > 8#77# then
C : Integer (Count and 8#77#);
else
C : Integer (Count);
end if Count;

end private;

begin
D.Shift_Left(C);

-- Set the register to new value
R := D.Ru;

end shift_left_logical;

</code>

For the double-register shift

<code>

-- Circular left shift double register
procedure double_shift_left_logical (R1 : <> Word'Class,
R2 : <> Word'Class,
Count : in Integer) is
private
D1 : D_Word;
D2 : D_Word;

-- Normal register order so lower register bits go to upper register
D1.Ru := R1; -- A register
D1.Rl := R2; -- Q register

-- Reverse the register order so high register bits go to low register
D2.Ru := R2; -- Q register
D2.Rl := R1; -- A register

-- NOTE: Only bits 5 .. 0 are used, the rest are ignored
-- Add option to limit actual shifting to mod 60 since anything
-- over 60 would just wrap around again
-- ex: Count == 61 is equivalent to Count == 1
-- on a 60-bit register
if Count > 8#77# then
C : Integer (Count and 8#77#);
else
C : Integer (Count);
end if Count;

end private;

begin
D1.Shift_Left(C); -- AQ register pair
D2.Shift_Left(C); -- QA register pair

-- Set the registers to new values
R1 := D1.Ru; -- A register
R2 := D2.Ru; -- Q Register

end shift_left_logical;

</code>

This is from my limited understanding so far.

Thanks for all of your help - I wish I could catch on a little quicker, but age and $DayJob (plus $HoneyDoList) make learning a new language a little challenging lately.

Ken Roberts

unread,
Jan 25, 2020, 5:44:24 AM1/25/20
to
On Saturday, January 25, 2020 at 2:37:17 AM UTC-8, Ken Roberts wrote:
<snip>

Before I get nitpicked:
>
> For the double-register shift
>
> <code>
>
> -- Circular left shift double register
> procedure double_shift_left_logical (R1 : <> Word'Class,
> R2 : <> Word'Class,
> Count : in Integer) is

<snip>

> end shift_left_logical;

>
> </code>

Yes - this should be

end double_shift_left_logical;

C&P w/memory fart

Shark8

unread,
Jan 25, 2020, 3:26:27 PM1/25/20
to
On Saturday, January 25, 2020 at 3:37:17 AM UTC-7, Ken Roberts wrote:
> It might be that I can only really concentrate on learning Ada on my weekends (thursday night -> saturday) subject to $HoneyDoList.
>
> Needless to say, I'm not always the brightest nowadays between age and free time without some extra help, so sorry for seeming a little dense right now.
That's fine; we all know life happens.

>
> Here's the computer representation as expressed in documentation and how I'm used to working on the hardware:
>
> 30-bit words (both memory storage and hardware registers)
>
> Bit Number : 29 28 .. 01 00
> MSBit on left, LSBit on right
Isn't MSB on left, rather arbitrary?
(eg "Your left or mine?")

What really matters is that you're consistent.

> Instruction format:
>
> Bit number: | 29 .. 24 | 23 .. 21 | 20 .. 18 | 17 .. 15 | 14 .. 0 |
> Field : | Op code | J | K | B | Y |
>
> Op code : Instruction
> j : Skip next instruction modification
> k : Source or destination of data
> b : Index register to use to modify Y
> y : Constant or memory address

I would recommend that you use a proper enumeration for your op-codes, and a full record for your instruction. The enumeration can help by making use of the required case-coverage (absent an OTHERS option) for case-statements and discriminated-records; the record is an excellent way to "keep things together" and organize things. — If you *do* use a record for the instruction-type, consider a record discriminated on the op-code enumeration.

>
> Based on Niklas Holsti's recommendation, it looks like the following would be what I want to do:
>
> <code>
>
> with Interfaces;
> package core is
I_Word's syntax is off you can only do "Type X is new Y with record ..." for tagged types. — in order to make it work as you have it laid out you're going to need the appropriate sized types to occupy those locations.

> -- Circular left shift register
> procedure shift_left_logical (R : <> Word'Class,
> Count : in Integer) is
You can only use S'Class on tagged types; while you /could/ have your word-types as tagged-types, it's probably a really bad idea considering you indicate you want to model the bits. (Tagged-types have a tag which would throw off your record's bit-layouts.)

>
> Thanks for all of your help - I wish I could catch on a little quicker, but age and $DayJob (plus $HoneyDoList) make learning a new language a little challenging lately.
Again, it's fine.

Ken Roberts

unread,
Jan 27, 2020, 9:10:41 AM1/27/20
to
After some more reflection (nice thing about having a boring job), I think I've been overthinking the problem.

The registers do not need to be a subtype of Unsigned_32 (or Word30), since they're going to be containers rather than actual variables. As long as the __data__ they get/return is a subtype of Unsigned_32, the conversion is automatic (from my understanding).

So, for what I'm looking at, I_Word[1-2] is only used by one register (the Instruction register), so they can be worked on using private variables; once the instruction is loaded into the register, the upper half is pretty much read-only with the lower half being the only part that _may_ be returned. The other registers pretty much just use Unsigned_30 as is. For the ones that use half-word (15-bit) parts, easy enough to work with as well since they'll be using subtypes of Unsigned_30.

Goes to show what happens when you over-think the problem.

Thanks for all of your suggestions and help.
Reply all
Reply to author
Forward
0 new messages