Regen

Regen is a code generator. It takes a register set description and generates a C++ code to work with register values.


CCore-Regen.exe <input file> <output file>

Here is a sample description:


reg ISR : 8 // interrupt status register
 {
  A : 0
  B : 1
  C : 2
  D : .
 }

reg CTRL : 32
 {
  RUN   : 0
  STOP  : 1
  COUNT : 2-5

  * : .+10

  MODE  : 16-18
   {
    SLOW = 0
    FAST = 1
    ACK  = 7
   }
 }

reg STAT : 16
 {
  RUN   : 0
  STOP  : 1
  COUNT : .+4
  
  *     : 8+2

  MODE  : .+3
   {
    SLOW = 0
    FAST = 1
    ACK  = 100b
   }
 }                  

reg TEMP : 16 {}
 
bar Test : address
 {
  ISR  =  10    RO

  IMR  =  .     WO as ISR

  CTRL =  .      

  EXT[10] = . as CTRL

  STAT =  .     RO

  TEMP = hidden
 }

It defines a set of registers and a register bar. Register is defined as:


reg CTRL : 32
 {
  RUN   : 0
  STOP  : 1
  COUNT : 2-5

  * : .+10

  MODE  : 16-18
   {
    SLOW = 0
    FAST = 1
    ACK  = 7
   }
 }

The keyword reg is followed by a register name and a register bit length. Bit length can be 8, 16 or 32. Then bits description follows enclosed in figure brackets. A bits description defines single bits and bit fields. A single bit is assigned a bit number (from LSB to MSB). A bit number may be given as the dot. The dot denotes the current bit. A bit field can be given in the following forms: as first-last bits, as bit+length or as .+length bits. In the last two forms length is the length of the bit field in bits. The asterisk instead of the bit name can be used to insert unused bits. A bit field may have an associated named value list. Values can be given in the decimal, hexadecimal (with the suffix h) or binary (with the suffix b) forms. If the bits description is empty, the register is assumed untyped.

A register bar is a set of registers as a unit. Hardware units are usually controlled using register bars. Methods of reading and writing registers may vary. It can be memory-mapped registers, or the register access may involve some function calls and, in fact, can be quite expensive. Regen generates a helper class to abstract from the exact register access process.

Register bar has a register refer mode: address or index. Dot denotes the current address or index. Bar description is a list of registers or register blocks with its properties. The first property is the address (or index). It can be given explicitly or implicitly. If the address mode is used, then the next address is defined using the register byte length. The next attribute is the access mode. It can be RO, WO, RW or hidden, RW by default. Finally, a register type can be given using the directive as. Multiple registers can share the same register structure.

Regen generates the header file.


/* Test.gen.h */ 

....

/* struct Type_CTRL */ 

enum Bits_CTRL : uint32
 {
  CTRL_RUN  = 0x00000001,
  CTRL_STOP = 0x00000002
 };
 
inline Bits_CTRL operator | (Bits_CTRL a,Bits_CTRL b)
 { return Bits_CTRL(uint32(a)|uint32(b)); }
 
enum Field_CTRL_MODE : uint32
 {
  CTRL_MODE_SLOW = 0x00,
  CTRL_MODE_FAST = 0x01,
  CTRL_MODE_ACK  = 0x07
 };

struct PrintField_CTRL_MODE
 {
  Field_CTRL_MODE field;

  explicit PrintField_CTRL_MODE(Field_CTRL_MODE field_) : field(field_) {}
 
  template <class P>
  void print(P &out) const
   {
    switch( field )
      {
       case 0x00 : Putobj(out,"SLOW"); break;
       case 0x01 : Putobj(out,"FAST"); break;
       case 0x07 : Putobj(out,"ACK"); break;

       default: Putobj(out,uint32(field));
      }
   }
 };
 
inline PrintField_CTRL_MODE GetTextDesc(Field_CTRL_MODE field)
 {
  return PrintField_CTRL_MODE(field);
 }

For the register CTRL the following entities are generated: enum Bits_CTRL — register bits, enum Field_CTRL_MODE — values for the field MODE. Finally, the type Type_CTRL to represent and manipulate the register value.


struct Type_CTRL
 {
  using Type = uint32 ;

  Type value;


  explicit Type_CTRL(Type value_=0) : value(value_) {}
 

  operator Type() const { return value; }
 
  void operator = (Type value_) { value=value_; }

The type is a wrapper over an unsigned integral type.


  template <class Bar>
  Type_CTRL & setTo(Bar &bar) { bar.set_CTRL(*this); return *this; }
 

  template <class Bar,class AddressType>
  Type_CTRL & setTo(Bar &bar,AddressType ind) { bar.set_CTRL(ind,*this); return *this; }
 

  template <class T>
  Type_CTRL & set(T to) { to(*this); return *this; }
 

The group of set methods can be used to set the value to the register bar or to some "setter".


  Type_CTRL & setbit(Bits_CTRL bits) { value|=Type(bits); return *this; }
 
  Type_CTRL & setbitIf(bool cond,Bits_CTRL bits) { if( cond ) value|=Type(bits); return *this; }
 
  Type_CTRL & clearbit(Bits_CTRL bits) { value&=~Type(bits); return *this; }
 
  Type_CTRL & clearbitIf(bool cond,Bits_CTRL bits) { if( cond ) value&=~Type(bits); return *this; }
 
  Type maskbit(Bits_CTRL bits) const { return value&bits; }
 
  bool testbit(Bits_CTRL bits) const { return (value&bits)==Type(bits); }
 

The group of bit-manipulation methods can be used to change bits or bit testing.


  Type get_COUNT() const;
 
  Type_CTRL & set_COUNT(Type field);
 

  Field_CTRL_MODE get_MODE() const;
 
  Type_CTRL & set_MODE(Field_CTRL_MODE field);
 

Another methods to get or set register bit fields. Bit and field change methods are self-modifying and return a reference to the object, so they can be chained.


  template <class P>
  void print(P &out) const;
 };

Finally, the print method is provided to print the value in a verbose manner.


/* type Type_TEMP */ 

using Type_TEMP = uint16 ;

Untyped register generates only a typedef.

Register bar generates a register bar class.


/* struct Test<RW> */ 

template <class RW>
struct Test
 {
  RW rw;

  using AddressType = typename RW::AddressType ;

  template <class ... TT>
  explicit Test(TT && ... tt) : rw( std::forward<TT>(tt)... ) {}

The inner object rw is a mean to read or write a register. It must implement the following methods:


class RW
 {
   ....

  public:

   using AddressType = .... ; 

   template <class UInt>
   UInt get(AddressType address);
   
   template <class UInt>
   void set(AddressType address,UInt value);
 };

Methods get() and set() are used with uint8, uint16, uint32 template parameters.


  template <class T>
  struct Setter
   {
    RW &rw;
    AddressType address;

    Setter(RW &rw_,AddressType address_) : rw(rw_),address(address_) {}

    void operator () (T value) { rw.set(address,value.value); }
   };

Setter classes are used to set a register value. They bind the read-writer and the register address.


  //--- CTRL

  Type_CTRL get_CTRL();
 
  void set_CTRL(Type_CTRL value);
 
  Setter<Type_CTRL> to_CTRL();
 
  static Type_CTRL null_CTRL();
 
  static Type_CTRL ones_CTRL();

get_CTRL() reads a register value.

set_CTRL() writes a register value.

to_CTRL() binds the read-writer and the register address and returns the register setter.

Static methods null_CTRL() and ones_CTRL() create a register value with all bits equals null or one.


  //--- EXT

  Type_CTRL get_EXT(AddressType ind);
 
  void set_EXT(AddressType ind,Type_CTRL value);
 
  Setter<Type_CTRL> to_EXT(AddressType ind);
 

Block register methods have the additional argument — the register index.


  //--- ISR

  Type_ISR get_ISR();
 
  static Type_ISR null_ISR();
 
  static Type_ISR ones_ISR();
 

Read-only registers have no set methods.


  //--- IMR

  void set_IMR(Type_ISR value);
 
  Setter<Type_ISR> to_IMR();
 

Write-only registers have no get methods.


  //--- TEMP

 };
 

Hidden registers have no methods.