HOME       UP       PREV       NEXT (Transactional Level Modelling (TLM))  

Using C Preprocessor to Adapt Firmware

We may need to recompile the hardware/software interface when compiling for TLM model as compared to the actual firmware.

For a 'mid-level model', differences might be minor and so implemented in C preprocessor.

Device driver access to a DMA controller might be changed as follows:

#define DMACONT_BASE       (0xFFFFCD00) // Or other memory map value.
#define DMACONT_SRC_REG    0            
#define DMACONT_DEST_REG   4
#define DMACONT_LENGTH_REG 8            // These are the offsets of the addressable registers
#define DMACONT_STATUS_REG 12

#ifdef ACTUAL_FIRMWARE

  // For real system and lower-level models:
  // Store via processor bus to DMACONT device register
  #define DMACONT_WRITE(A, D)    (*(DMACONT_BASE+A*4)) = (D) 
  #define DMACONT_READ(A)        (*(DMACONT_BASE+A*4))

#else

  // For high-level TLM modelling:
  // Make a direct subroutine call from the firmware to the DMACONT model.
  #define DMACONT_WRITE(A, D)    dmaunit.slave_write(A, D)
  #define DMACONT_READ(A)        dmaunit.slave_read(A)

#endif

// The device driver will make all hardware accesses to the unit using these macros.
// When compiled native, the calls will directly invoke the behavioural model, bypassing the bus model.
Behavioural model example (the one-channel DMA controller from earlier):

  // Behavioural model of 
  // slave side: operand register r/w. 
  uint32 src, dest, length; 
  bool busy, int_enable;

  u32_t status() { return (busy << 31)
            | (int_enable << 30); }    

  u32_t slave_read(u32_t a) 
  { 
    return (a==0)? src: (a==4) ? dest: 
     (a==8) ? (length) : status();
  }
  void slave_write(u32_t1 a, u32_t d) 
  { 
     if (a==0) src=d; 
     else if (a==4) dest=d; 
     else if (a==8) length = d; 
     else if (a==12)
     { busy = d >> 31; 
       int_enable = d >> 30; }
  }
  // Bev model of bus mastering portion.
  while(1)
  { 
    waituntil(busy);
    while (length-- > 0)
      mem.write(dest++, mem.read(src++));
    busy = 0;
  }

Like to make interrupt output with an RTL-like continuous assignment:

   interrupt = int_enable & !busy;
But this will need a thread to run it, so this code must be placed in its own C macro that is inlined at all points where the supporting expressions might change.

A full example is in the `ethercrc.zip' folder on the course web site (and unzipped on PWF).

Alternatively, it is also possible to use the workstation VM system to trap calls from natively-compiled firmware to hardware: this requires the memory map of the embedded system to resemble that of the workstation.


4: (C) 2008-13, DJ Greaves, University of Cambridge, Computer Laboratory.