6

So I'm new to Ada, and I'm attempting to write a kernel in it, but I cannot seem to find any good information on how to do this properly. In C, I would write:

unsigned char* videoram = (char*) 0xB8000;
videoram[0] = 65;

to access the video ram directly and write 'a' to it. I've heard I need to use an Ada array and other pragma's to do this in a typesafe manner in Ada. Is there any good resources on this kind of Ada programming?

MarkHammons
  • 123
  • 1
  • 5
  • A further thought to oenones answer (and my contributions) - if you are doing text only processing and you know your screen bounds, you could define your array to be a 2D array and be able to directly access row&column positions without calculating the memory offset each time. (just be careful regarding row or column major issues (which comes first and how that maps to your memory segment)) – NWS May 30 '12 at 12:47

3 Answers3

6

You can use the 'Address attribute:

Videoram : String (1 .. Videoram_Size);
for Videoram'Address use 16#B8000#;
-- ...
Videoram (1) := 'a';

If you don't want to use String and Characters, you can define your own data types.. like:

type Byte is mod 2**8; -- unsigned char
type Byte_Array is array (Natural range <>) of Byte;
Videoram : Byte_Array (0 .. Videoram_Size - 1);
for Videoram'Address use 16#B8000#;
-- ...
Videoram (0) := 65;

Btw, you even get range checking for the index, so you can't write outside of the Videoram range.

Rommudoh
  • 1,844
  • 10
  • 11
  • Is there a typesafe way to define a 2byte type with the first byte being a char and the second a color byte? Because that's the actual format the video ram takes per character. – MarkHammons May 30 '12 at 08:43
  • @MarkHammons Im a little confused. Did you mean "video ram takes per character _pair_" ? (Last time i checked a byte was the size of a character!) – NWS May 30 '12 at 09:42
  • @NWS yes, per character, the videoram takes a pair of bytes. The first byte is the actual character, and the second byte defines the background and foreground color. 0x00 is black on black for example. In C, " videoram[0] = 65; videoram[1] = 0x00; videoram[2] = 66; videoram[3] = 0x00;" prints ab in black with a black background. – MarkHammons May 30 '12 at 11:12
  • you could use a record for that and specify the record layout by a record representation clause, described in RM 13.5.1 and maybe define the 'size to 2*2**8. – Rommudoh May 30 '12 at 11:12
  • 1
    @MarkHammons : (16 bit chars ok im with you now!) oenone is correct, an array of records, using a representation clause is the way to go. See [this](http://en.wikibooks.org/wiki/Ada_Programming/Type_System#Checked_conversion_for_non-numeric_types) for an example of how to do representation clauses. You can therefore define a record type with elements for character, foreground colour & background colour, specifying the bit positions for each, define an array of them, and set the start of the array as suggested. – NWS May 30 '12 at 11:49
5

If you use an address attribute (i.e. for Object'Address use ... ), you should use the To_Address() function found in System.Storage_Elements because the Address type doesn't have to be an integer. The Ada Reference Manual only states:

"Address is a definite, nonlimited type with preelaborable initialization"

Whereas for the Integer_Address type in System.Storage_Elements it states:

"Integer_Address is a (signed or modular) integer subtype. To_Address and To_Integer convert back and forth between this type and Address."

So, you should really use:

for Object'Address use To_Address( 16#B8000# );

One last thing to point out from T.E.D's answer is that if you are concerned about object initialization using this method, simply add a pragma Import( Ada, your_object ) after the declaration so that default initialization is suppressed.

  • This probably could have been done as comments on the other two answers. However, you are new and can use the rep, so +1 for you. :-) – T.E.D. Jun 01 '12 at 13:21
4

There are actually two ways.

One is to set a pointer to the address you want to use, and access the object via the pointer.

type Video_RAM_Pointer is access all My_Video_Ram_Struct;
package Convert is new System.Address_To_Access_Conversions (Video_RAM_Pointer);
Video_RAM : constant Video_RAM_Pointer := Convert.To_Access (16#B8000#);

The other is to overlay your data right on top of the location.

Video_RAM : My_Video_RAM_Struct;
for Video_RAM'address use at 16#B8000#;

Generally, I prefer using the former. Among other issues, the latter counts as a declaration, which means that any fields in My_Video_RAM_Struct that have initialization code will get reinitialized every time you declare your overlay. Additionally, it is tempting to folks to overuse (abuse) that feature to alias objects all over the place, which is both hard on the optimizer and hard on the maintanence programmer.

The pointer method just tells the compiler to assume the given address holds the structure you told it, which IMHO is exactly what you want to happen.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • This should be `Convert.To_Pointer`, and the parameter passed to initialise`System.Address_To_Access_Conversions` should be the base type and not the access type. Also, I'm pretty sure it should be `Video_RAM : constant Convert.Object_Pointer`... That's the only way I can get this code to compile using GNAT and targeting ada2012. – ajxs Apr 22 '19 at 10:45