C-macro: установить поле регистра, заданное битовой маской, на заданное значение - PullRequest
2 голосов
/ 26 мая 2010

У меня есть 32-битные регистры с полем, определенным как битовые маски, например,

#define BM_TEST_FIELD 0x000F0000

Мне нужен макрос, который позволяет мне установить поле (определенное его битовой маской) регистра (определенное его адресом) в данное значение. Вот что я придумал:

#include <stdio.h>
#include <assert.h>

typedef unsigned int u32;

/* 
 * Set a given field defined by a bit-mask MASK of a 32-bit register at address
 * ADDR to a value VALUE.
 */
#define SET_REGISTER_FIELD(ADDR, MASK, VALUE)                                      \
{                                                                                  \
  u32 mask=(MASK); u32 value=(VALUE);                                              \
  u32 mem_reg = *(volatile u32*)(ADDR); /* Get current register value           */ \
  assert((MASK) != 0);                  /* Null masks are not supported         */ \
  while(0 == (mask & 0x01))             /* Shift the value to the left until    */ \
  {                                     /* it aligns with the bit field         */ \
    mask = mask >> 1; value = value << 1;                                          \
  }                                                                                \
  mem_reg &= ~(MASK);                   /* Clear previous register field value  */ \
  mem_reg |= value;                     /* Update register field with new value */ \
  *(volatile u32*)(ADDR) = mem_reg;     /* Update actual register               */ \
}

/* Test case */
#define BM_TEST_FIELD 0x000F0000
int main()
{
  u32 reg = 0x12345678;
  printf("Register before: 0x%.8X\n", reg);/* should be 0x12345678 */
  SET_REGISTER_FIELD(&reg, BM_TEST_FIELD, 0xA);
  printf("Register after: 0x%.8X\n", reg); /* should be 0x123A5678 */
  return 0;
}

Есть ли более простой способ сделать это?

РЕДАКТИРОВАТЬ : в частности, я ищу способ уменьшить требования к вычислениям во время выполнения. Есть ли способ заставить препроцессор вычислить количество требуемых сдвигов влево для значения?

Ответы [ 4 ]

4 голосов
/ 27 мая 2010

РЕДАКТИРОВАТЬ: в частности, я ищу способ уменьшить требования к вычислениям во время выполнения. Есть ли способ чтобы препроцессор вычислил количество требуемых сдвигов влево для значения?

Да:

value *= ((MASK) & ~((MASK) << 1))

Это умножает value на младший установленный бит в MASK. Известно, что множитель имеет постоянную степень 2 во время компиляции, поэтому он будет скомпилирован как простой сдвиг влево любым удаленно исправным компилятором.

2 голосов
/ 26 мая 2010

Я бы рассмотрел , используя битовые поля для "форматирования" битов для аппаратного обеспечения, например ::100100

#include <stdio.h>
#include <inttypes.h>

struct myregister {
    unsigned upper_bits:12;
    unsigned myfield:4;
    unsigned lower_bits:16;
};

typedef union {
    struct myregister fields;
    uint32_t value;
} myregister_t;

int main (void) {
    myregister_t r;
    r.value = 0x12345678;
    (void) printf("Register before: 0x%.8" PRIX32 "\n", r.value);
    r.fields.myfield = 0xA;
    (void) printf("Register after: 0x%.8" PRIX32 "\n", r.value);
    return 0;
}

Редактировать: Обратите внимание на последующее обсуждение в комментариях. Существуют веские аргументы против использования битовых полей, но, на мой взгляд, это также полезно (особенно в синтаксисе, который я очень ценю). Нужно решить, исходя из обстоятельств, в которых будет использоваться код.

2 голосов
/ 26 мая 2010

Почему бы просто не поместить маску и значение в нужное место?

#define BM_TEST_FIELD (0xfUL << 16)
#define BM_TEST_VALUE (0xaUL << 16)
#define mmioMaskInsert(reg, mask, value) \
   (*(volatile u32 *)(reg) = (*(volatile u32 *)(reg) & ~(mask)) | value)

Тогда вы можете просто использовать его как:

mmioMaskInsert(reg, BM_TEST_FIELD, BM_TEST_VALUE);

Наверняка то, что у вас там, очень опасно. Запись в регистр часто может иметь побочные эффекты, и эти операции:

mem_reg &= ~(MASK);
mem_reg |= value;

фактически пишут в регистр дважды, а не один раз, как вы, вероятно, намереваетесь. Кроме того, почему не поддерживается маска 0? Что если я захочу записать во весь регистр (совпадение по таймеру или что-то в этом роде)? У вас есть другой макрос для этой операции? Если так, почему бы не использовать его как часть этой системы?

Еще одно замечание - возможно, было бы неплохо применить маску к значению, прежде чем помещать ее в регистр, в случае, если кто-то передает значение, которое имеет больше бит, чем маска. Что-то вроде:

#define maskInsert(r, m, v) \
  (*(volatile u32 *)(r) = (*(volatile u32 *)r & ~(m)) | ((v) & ~(m)))
1 голос
/ 26 мая 2010

Если вы настаиваете на этом конкретном интерфейсе (положение поля определяется маской), то, вероятно, единственное, что можно изменить / улучшить в вашей реализации, - это цикл, в котором вы перемещаете значение в правильную позицию ( выровнять его по маске). По сути, вам нужно найти смещение , выраженное в количестве битов, и сдвинуть значение, оставив это количество битов. Для выполнения этой операции вы использовали простой цикл, и вместо явного вычисления смещения в битах вы просто сдвигали значение влево на 1 бит на каждой итерации. Это будет работать Однако это может показаться неэффективным, особенно для полей, которые находятся в верхней части регистра, поскольку для них потребуется больше итераций цикла сдвига.

Для повышения эффективности вы также можете использовать любой из довольно известных, потенциально более эффективных методов для вычисления значения смещения, как описано в на этой странице . Я не знаю, стоит ли это усилий в вашем случае, хотя. Это может сделать ваш код более эффективным, но может сделать его менее читабельным. Решай сам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...