Memory Mapped Registers

In the previous exercise, you implemented a small model and had a brief interaction with the TEMU object system. This section will expand on the previous knowledge and provide the first practical working model which expose memory mapped registers to actual target software.

We will start with the previous model and extend it with:

  • Property Accessor For the Registered Property

  • Memory Access Interface

For this exercise we should rename our model as mmiomodel.c, and when compiling ensure the output is named libmmio.so.

A property will have a default accessor created for itself if no accessor is provided, the previous example registered the property a, with the MyModel-class, but provided NULL instead of a write and read accessor.

The first step now is to add accessor functions:

Add #include "temu-c/Support/Logging.h to your model include directives.

Implement the accessor functions:

static void static void
writeA(void *obj, temu_Propval pv, int idx)
{
  temu_logInfo(model, "updating a from %u to %u", model->a,
    pv.u32);
  model->a = pv.u32;
}
static temu_Propval
readA(void *obj, int idx)
{
  MyModel *model = (MyModel*)obj;
  temu_logInfo(model, "reading a as %u", model->a);
  return temu_makePropU32(model->a);
}

Then change the line:

temu_addProperty(c, "a", offsetof(MyModel, a), teTY_U32, 1,
  NULL, NULL,
  "property a");

To:

temu_addProperty(c, "a", offsetof(MyModel, a), teTY_U32, 1,
  writeA, readA,
  "property a");

Now, recompile the plug-in and load it again and modify the property manually.

temu> object-create class=MyModel name=foo
args="baz:42,foobar:42.0,blah:bar3"
temu> object-prop-write prop=foo.a val=42
:info: foo : updating a from 0 to 42
temu> object-prop-read prop=foo.a
:info: foo : reading a as 42
42

As you can see, when writing or reading a property, the property accessor will be called. This allows us to implement registers as properties with accessors (which especially simplify model testing, as one do not need to access registers from embedded software doing memory accesses). The example above also illustrated the use of the TEMU logging system.

To finalise our model for integration in the TEMU memory system, we need to add an implementation of the memory access interface. This interface is defined in temu-c/Memory/Memory.h, which needs to be included.

static void
memRead(void *obj, temu_MemTransaction *mt)
{
  temu_Propval pv;
  mt->Value = 0;
  switch (mt->Offset) {
  case 0:
    pv = readA(obj, 0);
    break;
  default:
    temu_logWarning(obj,
      "register at offset %d not implemented for reads",
      (int)mt->Offset);
     return;
  }
  mt->Value = temu_propValueU32(pv);
  mt->Cycles = 100;
}
static void
memWrite(void *obj, temu_MemTransaction *mt)
{
  temu_Propval pv = temu_makePropU32(mt->Value);
  switch (mt->Offset) {
  case 0:
    writeA(obj, pv, 0);
    break;
  default:
    temu_logWarning(obj,
      "register at offset %d not implemented for writes",
      (int)mt->Offset);
    return;
  }
  mt->Cycles = 100;
}

temu_MemAccessIface MemAccessIface = {
  NULL,
  memRead,
  memWrite,
  NULL
};

And add the following in the plug-in initialisation function:

temu_addInterface(c, "MemAccessIface",
                  TEMU_MEM_ACCESS_IFACE_TYPE,
                  &MemAccessIface, 0,
                  "memory access interface");

Compile the model, restart TEMU and run the following commands:

temu> exec ut700.temu
temu> import mmio
temu> object-create class=MyModel name=foo

The model then needs to be mapped to memory, this is done with the "memory-map" command, but first, let’s see which address is free in the UT700 system that you just created, some classes implement a pretty printing function in the (optional) object interface, this pretty printer can be invoked using the object-print command, the memory space class has one such implementation that prints out existing memory mappings:

temu> object-print obj=mem0
Memory Space 'mem0'
Address     Length           Object
----------- ---------------- ------
0x000000000            1 MiB rom0
0x040000000          128 MiB ram0
0x080000000          256   B ftmctrl0
0x080000100          256   B apbuart0
0x080000200          256   B irqMp0
0x080000300          256   B gpTimer0
0x080000900          256   B gpio0
0x080000a00          256   B spw1
0x080000b00          256   B spw2
0x080000c00          256   B spw3
0x080000d00          256   B spw4
0x080000f00          256   B ahbstat0
0x0800ff000            4 KiB apbctrl0
0x0801ff000            4 KiB apbctrl1
0x0fff20000          256   B canoc0
0x0fff20100          256   B canoc1
0x0fffff000            4 KiB ahbctrl0

As can be seen, after ahbstat0, there is some free space: 0x80000f00 + 256 = 0x80001000, and this area is not mapped.

temu> memory-map memspace=mem0 object=foo addr=0x80001000 length=256
:info: foo : map device at 0x80001000 – 0x80001100

If you execute the object-print command again, you should now see the newly mapped device with the name foo.

tion, we can use the built in assembler to create some instructions that can read and write to the register at offset 0 from the mapping, the offset is used in the switch. We create a small assembler program to trigger the memory accesses by using the built in assembler:

temu> assemble cpu=cpu0 instr="st %g1, [%g2 + 0]"
temu> assemble cpu=cpu0 instr="ld [%g2 + 0], %g3"

The program will by default end up at address 0 and 4. Enter the command disassemble cpu=cpu0 addr=0 count=10 and see that you got the correct instructions assembled in memory. The next step is to set the %g2 register to the address of the device’s register and then set some value to write to the register in %g1:

temu> cpu-set-reg cpu=cpu0 reg="%g2" value=0x80001000
temu> cpu-set-reg cpu=cpu0 reg="%g1" value=42
temu> step cpu=cpu0 steps=2
:info: foo : updating a from 0 to 42
:info: foo : reading a as 42

Congratulations, your first TEMU MMIO device is now working!