Я пытаюсь закодировать очень простой язык ассемблера на C.
Каждая инструкция имеет 32 бита, первые 8 из которых являются кодом операции, а следующие 24 содержат непосредственное значение (если инструкция содержит единицу. В противном случае это просто нули).
Коды операций определяются как простые числа. Например, PUSHC
определяется как 1
.
Теперь я хочу проверить, правильно ли разделены код операции и непосредственное значение.
Я написал следующие определения:
#define SIGN_EXTEND(i) ((i) & 0x00800000 ? (i) | 0xFF000000 : (i)) // Handles negative Immediate Values (i)
#define IMMEDIATE(x) SIGN_EXTEND(x & 0x00FFFFFF) // Returns the Immediate Value of an instruction (x)
#define OPCODE(x) (x >> 24) // Returns the Opcode of an instruction (x)
#define OP(o) ((o) << 24) // An instruction with an Opcode (o)
#define OPI(o, i) (OP(o) | IMMEDIATE(i)) // An instruction with an Opcode (o) and an Immediate Value (i)
В методе void runProg(uint32_t prog[]) {...}
я передаю массив, содержащий инструкции для программы на ассемблере, по порядку, разделенные запятыми, например:
uint32_t progTest[] = {OPI(PUSHC, 3), OPI(PUSHC, 4}, OP(ADD), OP(HALT)};
runProg(progTest);
Вот что делает runProg
:
void runProg(uint32_t prog[]) {
int pc = -1; // the program counter
uint32_t instruction;
do {
pc++;
instruction = prog[pc];
printf("Command %d : 0x%08x -> Opcode [%d] Immediate [%d]\n",
pc, instruction, OPCODE(instruction), IMMEDIATE(instruction));
} while (prog[pc] != OP(HALT));
}
Таким образом, он выводит полную команду в виде шестнадцатеричного числа, за которым следует только код операции, за которым следует только непосредственное значение. Это работает для всех инструкций, которые я определил. Вышеприведенная тестовая программа дает следующий результат:
Command 0 : 0x01000003 -> Opcode [1] Immediate [3]
Command 1 : 0x01000004 -> Opcode [1] Immediate [4]
Command 2 : 0x02000000 -> Opcode [2] Immediate [0]
Command 3 : 0x00000000 -> Opcode [0] Immediate [0]
Теперь вот проблема:
Команда PUSHC
работает только с положительными значениями.
Изменение непосредственного значения первого вызова PUSHC
на -3
дает следующий результат:
Command 0 : 0xfffffffd -> Opcode [255] Immediate [-3]
Command 1 : 0x01000004 -> Opcode [1] Immediate [4]
Command 2 : 0x02000000 -> Opcode [2] Immediate [0]
Command 3 : 0x00000000 -> Opcode [0] Immediate [0]
Как видите, непосредственное значение отображается правильно, однако в команде отсутствует код операции.
Изменение определения IMMEDIATE(x)
от
#define IMMEDIATE(x) SIGN_EXTEND(x & 0x00FFFFFF)
к
#define IMMEDIATE(x) (SIGN_EXTEND(x) & 0x00FFFFFF)
дает прямо противоположный результат, когда код операции правильно разделен, но непосредственное значение неверно:
Command 0 : 0x01fffffd -> Opcode [1] Immediate [16777213]
Command 1 : 0x01000004 -> Opcode [1] Immediate [4]
Command 2 : 0x02000000 -> Opcode [2] Immediate [0]
Command 3 : 0x00000000 -> Opcode [0] Immediate [0]
Поэтому я относительно уверен, что мое определение SIGN_EXTEND(i)
ошибочно. Тем не менее, я не могу указать на это пальцем.
Любые идеи о том, как это исправить, приветствуются!
0x01fffffd
правильно; 8-битное поле кода операции содержит 0x01, а 24-битное непосредственное поле содержит 0xffffffd, что является 24-битным дополнительным кодом до двух для -3. При кодировании нет необходимости в расширении знака; макросу IMMEDIATE
передается (предположительно) 32-битное значение дополнения до двух, и его задача — просто уменьшить его до 24 бит, а не расширить. Так что это просто должно быть #define IMMEDIATE(x) ((x) & 0xffffff)
. Когда вы хотите интерпретировать непосредственное поле, как для печати, вам нужно преобразовать из 24 бит в 32. Для этого вам нужен другой макрос, как у вас есть разные макросы для кодирования кода операции (OPI
) и для декодирования /извлечь/интерпретировать код операции (OPCODE
).
Вы можете интерпретировать непосредственное поле с помощью этой функции:
static int InterpretImmediate(unsigned x)
{
// Extract the low 24 bits.
x &= (1u<<24) - 1;
/* Flip the sign bit. If the sign bit is 0, this
adds 2**23. If the sign bit is 1, this subtracts 2**23.
*/
x ^= 1u<<23;
/* Convert to int and subtract 2**23. If the sign bit started as 0, this
negates the 2**23 we added above. If it started as 1, this results in
a total subtraction of 2**24, which produces the two’s complement of a
24-bit encoding.
*/
return (int) x - (1u<<23);
}
OPI затирает код операции отрицательными немедленными значениями