Разрядность регистров

Последнее обновление: 01.03.2024

В прошлых темах мы посмотрели, как помещать в регистры какие-нибудь числа, копировать их из одного регистра в другой и так далее. Но при этом не учитывалась разрядность регистров. В качестве примера будем считать, что наши регистры - 32-разрядные. Соответственно больше 32 бит в них поместить нельзя. А поскольку в качестве языка программирования выбран язык Python, то числа, которыми мы манипулируем, потенциально могут иметь и гораздо большее количество разрядов. И нам надо что-то с этим сделать.

В Python нет, отдельных числовых типов для чисел разной разрядности, как в C или Java, и нам надо самостоятельно убирать старшие разряды, кроме первых 32. С одной стороны, это сделать легко, так как мы можем с помощью операции логического умножения применить маску 0xFFFFFFFF, которая отсеить все избыточные разряды:

num1 = 15
num1 = num1 & 0xFFFFFFFF
print(f"{num1}")    # 15

С другой стороны, нам надо учитывать интерпретацию чисел. Например:

num2 = -3
num2 = num2 & 0xFFFFFFFF
print(f"{num2}")        # 4294967293

После применения маски вместо отрицательного числа -3 мы получаем положительное число 4294967293. В то же время если мы посмотрим на 16-ричное представление числа после применения маски:

num2 = -3
num2 = num2 & 0xFFFFFFFF
print(f"{num2}")        # 4294967293
print(f"{num2:0x}")     # fffffffd

То мы увидим, что мы получаем корректное значение - 0xfffffffd при интерпретации как 32 разрядного числа со знаком как раз и представляет число -3. То есть мы имеем дело в реальности с число 0xfffffffd, а как его интерпретировать - как беззнаковое число 4294967293 или как число со знаком -3, это другое дело, которое зависит от контекста.

Возьмем программу из прошлой темы и добавим в нее нормализацию по разрядности:

instructions = []       # список инструкций
r = [0]*4       # значения 4 регистров
# карта сопоставления регистров и их индексов в списке r    
regs32 = {"r0":0, "r1":1, "r2":2, "r3":3}
pc = 0  # указатель на следующую инструкцию
# поддерживаемые инструкции и количество их операндов
mnemonics = {"mov":2, "add": 3, "sub":3, "mul": 3, "and": 3, "orr": 3}

# считываем файл hello.s в список инструкций
with open("hello.s", "r", encoding="utf8") as source:
    lines = source.readlines()
    # обрабатываем строки из файла
    for i in range(0,len(lines)):
        lines[i] = lines[i].split("//")[0] \
                    .replace(",", " ")  \
                    .strip().rstrip("\n") \
                    .lower()
        while "  " in lines[i]:             # заменяем несколько пробелов одним
            lines[i] = lines[i].replace("  ", " ")  
        if(lines[i]) == "": continue        # если получилась пустая строка, переходим к следующей строке

        tokens = lines[i].split(" ")      # разбиваем инструкцию на токены
        instructions.append(tokens)         # добавляем инструкцию в список instructions

# функций вывода состояния программы на консоль
def print_state(instruction):
    print(f"pc:{pc}", end="   ")        # выводим значение PC
    print(f"{instruction:<16}", end=" ")     # выводим текущую инструкцию
    for reg in regs32:                      # выводим регистры
        rInd = regs32[reg]
        print(f"{reg}:0x{r[rInd]:04x}", end="  ")
    print()

# получаем индекс регистра
def get_register_index(token, show_error):
    if (token in regs32): return regs32[token]
    if show_error: print("Некорректный регистр", token)
    return None

# получаем значение регистра
def get_register(token, show_error):
    rInd = get_register_index(token, show_error)
    if (rInd != None): return r[rInd]
    return None

# получаем литерал
def get_literal(token):
    try:
        result = 0
        if (token[0:2]=="0x"): result = int(token[2:],16)     # если 16-ричное число
        elif (token[0:2]=="0b"): result = int(token[2:],2)     # если двоичное число
        else: result= int(token)
        return result & 0xffffffff      # нормализуем литерал до 32 разрядов
    except ValueError:
        print("Некорректный токен", token)
    return None

# получаем операнд, который может быть регистром или литералом
def get_register_or_literal(token):
    reg = get_register(token, False)
    if (reg != None): return reg
    return get_literal(token)

# получаем тип инструкции
def get_opCount(tokens):
    if tokens[0] not in mnemonics:          # проверяем корректность инструкции
        print("Некорректная инструкция ", " ".join(tokens))
        return None 
    # получаем количество операндов для данной инструкции
    count = mnemonics[tokens[0]] 
    if count!= len(tokens[1:]):     # проверяем количество операндов
        print("Некорректное количество операндов для инструкции: ", " ".join(tokens))
        return None
    return count

# цикл обработки инструкций
while True:
    if pc >= len(instructions): break  # если инструкции закончились, то выход из цикла
    tokens = instructions[pc]     # получаем текущую инструкцию для выполнения
    pc = pc + 1                 # увеличиваем указатель инструкций

    opCount = get_opCount(tokens)   # получаем количество операндов
    if(opCount == None): break

    # получаем операнды
    op2, op3 = 0, 0
    
    # первый операнд всегда регистр
    op1=get_register_index(tokens[1], True)
    
    # если инструкция с 2-мя операндами, то второй операнд может быть регистром или литералом
    if(opCount==2): op2 = get_register_or_literal(tokens[2])
        
    # если инструкция с 3-мя операндами, то второй операнд может быть регистром
    # а третий операнд может быть регистром или литералом
    if(opCount==3):
        op2 = get_register(tokens[2], True)
        op3 = get_register_or_literal(tokens[3])
        
    # если какой-то параметр не установлен, завершаем цикл
    if(None in [op1, op2, op3]): break
       
    result = 0
    match tokens[0]: 
        case "mov": 
            result = op2
        case "add": 
            result = op2 + op3
        case "sub": 
            result = op2 - op3
        case "mul": 
            result = op2 * op3
        case "and": 
            result = op2 & op3
        case "orr": 
            result= op2 | op3
    r[op1]= result & 0xffffffff   # нормализуем число до 32 разрядов
    
    print_state(" ".join(tokens))    # логгируем состояние программы на консоль    

Прежде всего в функции get_literal усекаем переданный литерал до 32 разрядов:

def get_literal(token):
    try:
        result = 0
        if (token[0:2]=="0x"): result = int(token[2:],16)     # если 16-ричное число
        elif (token[0:2]=="0b"): result = int(token[2:],2)     # если двоичное число
        else: result= int(token)
        return result & 0xffffffff      # нормализуем литерал до 32 разрядов
    except ValueError:
        print("Некорректный токен", token)
    return None

В реальности в различных ассемблерах часто также действует некоторые ограничения на размер литерала. Некоторые ассемблеры генерируют ошибку, если значение литерала, скажем, больше 32-ти или какого-то другого количества разрядов. Здесь же мы просто отбрасываем старшие разряды.

Кроме того, нам надо следить, чтобы после выполнения инструкции помещаемое в целевой регистр число также представляло не больше 32 разрядов. Для этого также нормализуем значение с помощью маски 0xFFFFFFFF:

r[op1]= result & 0xffffffff   # нормализуем число до 32 разрядов

И поскольку теперь десятичное представление числа зависит от интерпретации, при выводе значения регистра в функции print_state заменяем его на шестнадцатеричное представление:

for reg in regs32:
    rInd = regs32[reg]
    print(f"{reg}:0x{r[rInd]:04x}", end="  ")

Для тестирования в файле hello.s определим следующую программу:

// тестовая программа на ассемблере
mov r1, -3                  // r1 = 0xFFFFFFFD
mov r0, 8                   // r0 = 0x08
mov r1, -1                  // r1 = 0xFFFFFFFF
mov r0, 255                 // r0 = 0xFF
mov r1, 0x1234              // r1 = 0x00001234
mov r0, 128                 // r0 = 0x80

Консольный вывод программы:

pc:1   mov r1 -3          r0:0x0000 r1:0xfffffffd r2:0x0000 r3:0x0000 
pc:2   mov r0 8           r0:0x0008 r1:0xfffffffd r2:0x0000 r3:0x0000 
pc:3   mov r1 -1          r0:0x0008 r1:0xffffffff r2:0x0000 r3:0x0000 
pc:4   mov r0 255         r0:0x00ff r1:0xffffffff r2:0x0000 r3:0x0000 
pc:5   mov r1 0x1234      r0:0x00ff r1:0x1234 r2:0x0000 r3:0x0000 
pc:6   mov r0 128         r0:0x0080 r1:0x1234 r2:0x0000 r3:0x0000 
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850