Исследуйте Каир: практичная и эффективная архитектура ЦП с поддержкой Turing-Complete и STARK
6 мая 2022 г.Cairo — это практически эффективная Тьюринг-полная архитектура ЦП, дружественная к STARK.
В этой статье мы представляем архитектуру ЦП Cairo с точки зрения структуры инструкций и перехода между состояниями, а также приводим несколько примеров инструкций.
Структура инструкции
Слово, изначально поддерживаемое ЦП Cairo, является элементом поля, где поле — это некоторое фиксированное конечное поле характеристики P>2^63.
Каждая инструкция будет занимать 1 или 2 слова. Если за инструкцией следует непосредственное значение([ap] = «12345678»), инструкция займет 2 слова, а значение будет сохранено во втором слове. Первое слово каждой инструкции состоит из следующих элементов:
- off_dst[bit0…15]: смещение адреса назначения, репрезентативное значение
-2^{15} + ∑_{i = 0}^{15} b_i . 2^i ∈ [-2^{15}, 2^{15});
- off_op0[bit16…31]: смещение адреса op0, репрезентативное значение
-2^{15} + ∑_{i = 0}^{15} b_i . 2^i ∈ [-2^{15}, 2^{15});
- off_op1[bit32…47]: смещение адреса op1, репрезентативное значение
-2^{15} + ∑_{i = 0}^{15} b_i . 2^i ∈ [-2^{15}, 2^{15});
- dst reg[bit48]: базовый регистр смещения адреса получателя, ap или fp;
- op0 reg[bit49]: базовый регистр смещения адреса op0, ap или fp;
- op1_src[bit50…52]: адрес op1,
- Случай: 000
op1=m(op0+off_op1)
- Дело: 001
op1=m(pc+off_op1)
- Случай: 010
op1=m(fp+off_op1)
- Дело: 100
op1=m(ap+off_op1)
- res_logic bit53…54]: вычислительная логика
- Регистр: 00
res = op1
- Случай: 01
res = op0 + op1
- Случай: 10
res = op0 ∗ op1
- pc_update[bit55…57]: логика обновления ПК
- Регистр: 000 // общий
следующий_пк = пк + размер_инструкции
- Случай: 001 // абсолютный переход
next_pc = res
- Case: 010 // относительный переход
next_pc = pc + res
- Случай: 100 // условный относительный переход
next_pc = pc + op1 (или размер_инструкции, если dst = 0)
- ap_update[bit58…59]: логика обновления ap
- Регистр: 00
next_ap = ap (или ap+2, если код операции = 1)
- Случай: 01
next_ap = ap + res
- Случай: 10
next_ap = ap + 1
- opcode[bit60…62]: типы опкодов
- Регистр: 000 // jmp
- Регистр: 001 // вызов
- Дело: 010 // рет
- Случай: 100 // утверждать
Комментарий к переходу состояния
Функция перехода состояния представляет собой общий блок перехода состояния (поскольку она содержит логику обработки всех типов инструкций), а вычисление обычно разбивается на несколько непрерывно выполняемых инструкций.
Поэтому нам необходимо:
- обеспечить содержание инструкции и достоверность состояний до и после выполнения инструкции (например, прохождение проверки определенного диапазона и проверки согласованности состояния) и
- убедиться, что выполняемая инструкция действительна.
Комментарий к логике перехода
Если состояние до и после выполнения инструкции согласовано, обновление состояния должно выполняться в соответствии со следующей логикой:
```javascript
Контекст: м(.).
Состояние ввода: (ПК, ап и фп).
Состояние вывода: (next_pc, next_ap и next_fp).
Вычислить op0.
Если op0_reg == 0: // Оценить базовый регистр op0 в соответствии со значением флага, 0 — это ap, 1 — это fp, и получить значение op0.
op0 = m(ap + off_{op0})
еще:
op0 = m(fp + off_{op0})
Вычислить op1 и размер инструкции.
switch op1_src: // Получить значение op1
случай 0:
инструкция_size = 1 // op1 = m[[ap/fp + off_op0] +off_op1], максимум с двухуровневым встраиванием.
op1 = m(op0 + off_{op1})
Дело 1:
инструкция_size = 2 // существуют непосредственные числа. Размер инструкции 2 слова.
op1 = m(pc + off_{op1})//
Если off_{op1} = 1, имеем op1 = немедленное_значение. // Например, [ap] = "12345678", op1 = "12345678"
случай 2:
инструкция_размер = 1 // op1 = [fp + off_op1]
op1 = m(fp + off_{op1})
случай 4:
инструкция_размер = 1 // op1 = [ap +off_op1]
op1 = m(ap + off_{op1})
По умолчанию:
Неопределенное поведение
Вычислить разрешение
если pc_update == 4: // jnz
if res_logic == 0 && код операции == 0 && ap_update != 1: // Назначить && прыжок && расширенный ап
res = Unused // При условном переходе значения флагов res_logic, opcode и ap_update могут быть только такими, как показано выше. В этом случае переменная res не будет использоваться.
еще:
Неопределенное поведение
иначе, если pc_update = 0, 1 или 2:
switch res_logic: // Вычислительная логика res:
случай 0: res = op1
случай 1: res = op0 + op1
случай 2: res = op0 * op1
по умолчанию: Неопределенное поведение
еще: Неопределенное поведение
Вычислить dst.
если dst_reg == 0:
dst = m(ap + offdst)
еще:
dst = m(fp + offdst)
Вычислить новое значение pc.
переключить pc_update:
case 0: # Общий случай: [ap] = 1
следующий_пк = пк + размер_инструкции
case 1: # Абсолютный прыжок: jmp abs 123
следующий_пк = разрешение
case 2: # Относительный переход: jmp rel [ap - 1]
next_pc = ПК + разрешение
case 4: # Условный относительный переход (jnz): jmp rel [ap - 1] if [fp - 7] = 0
следующий_ПК =
if dst == 0: pc + размер_инструкции // Если dst = 0, то pc выполняет обычные обновления; в остальном аналогично случаю 2.
else: pc + op1 // В этом случае res не используется, поэтому pc напрямую проводит + op1 вместо + res.
по умолчанию: Неопределенное поведение
Вычислить новое значение ap и fp на основе кода операции.
если код операции == 1:
Инструкция "Позвонить".
утверждать op0 == ПК + размер_инструкции
утверждать dst == fp
Обновить fp.
next_fp = ap + 2 // [ap] сохраняет текущее значение fp, [ap + 1] сохраняет пк после инструкции вызова; при выполнении инструкции ret удобно сбросить fp в [ap], а затем продолжить выполнение последующих инструкций.
Обновить ап.
переключить ap_update:
case 0: next_ap = ap + 2 // [ap] сохраняет значение fp, а [ap+1] сохраняет адрес инструкции после инструкции вызова; таким образом, ар увеличивается на 2.
по умолчанию: Неопределенное поведение
иначе, если код операции является одним из 0, 2, 4:
Обновить ап.
переключить ap_update:
case 0: next_ap = ap // [fp + 1] = 5
случай 1: next_ap = ap + res // расширенный ap [ap] += 123
случай 2: next_ap = ap + 1 // обычный [fp + 1] = [ap]; ап++
по умолчанию: Неопределенное поведение
код операции switch: // Внутри одной и той же функции значение fp остается неизменным.
случай 0:
следующий_кадр = кадр
случай 2:
инструкция "возврата".
next_fp = dst // Инструкция ret идет с инструкцией call и утверждает dst == fp.
случай 4:
Инструкция "подтвердить равенство".
утверждать res = dst
следующий_кадр = кадр
еще: Неопределенное поведение
Проверка инструкции
Как показано на рисунке 1, одна инструкция состоит из следующих элементов:
```javascript
инструкция по структуре :=
(off_dst: битвек 16)
(off_op0: битвек 16)
(off_op1: битвек 16)
(флаги: битвек 15)
Где:
off_dst[ бит0…15], off_op0[ бит16…31], off_op1[ бит32…47] находятся в диапазоне
-2^{15} + ∑_{i = 0}^{15} b_i . 2^i ∈ [-2^{15}, 2^{15})
Но в AIR мы преобразуем их в следующий вид:
˜выкл = выкл + 2^15
(Где * представляет один из dst, op0 и op1)
Таким образом, диапазон значений ˜off* должен быть [0,2^16)
Следовательно, для кодирования инструкции имеем:
Примечание. Вместо того, чтобы выделять 15 виртуальных столбцов длиной NN для 15-битных флагов инструкции, мы используем один виртуальный столбец.
˜f_i=∑_{j=i}^{14} 2j−i⋅fj
При длине 16Н, что соответствует следующим требованиям:
- ˜f_0 = ∑_{j=0}^{14} 2^{j-0}⋅f_j – 15-битное значение.
- ˜f_15 = 0
- ˜f_i −2˜f_{i+1} = ∑{j=i}^{14} (2^{j−i}⋅f_j) − 2⋅∑{j=i+1}^ {14}(2^{j−i−1}⋅fj) =∑_{j=1}^{14}(2j−i⋅fj) − ∑{j=i+1}^{14}(2j −i⋅fj)=fi
Следовательно, уравнение (1) можно записать в виде:
inst = ˜off_dst + 2^16⋅˜off_op0 + 2^32⋅˜off_op1 + 2^48⋅˜f0 ∈ [0,263)(2)
Поскольку характеристика конечного поля P > 2^63, одна инструкция может соответствовать только одному действительному элементу поля.
Поэтому для самой инструкции должны выполняться следующие ограничения:
Инструкция: inst= ˜off_dst + 2^16⋅˜off_op0 + 2^32⋅˜off_op1 + 2^48⋅˜f0
Бит: (˜f_i−2˜f_i +1)(˜f_i−2˜f_i+1 −1)=0 для всех i ∈ [0,15)
Последнее значение равно нулю: ˜f_15=0
Смещение находится в диапазоне: виртуальный столбец ˜off ∈ [0,2^16), поэтому off** ∈ [2^−15, 2^15)
Примеры инструкций
Подтвердить равенство
Инструкция утверждения равенства может быть выражена в следующем синтаксисе:
Это гарантирует, что обе части формулы равны; в противном случае выполнение программы будет отклонено.
Левая часть уравнения часто получается из [fp+off_dst] или [ap+off_dst], а правая часть имеет несколько возможных форм (reg_0 и reg_1 может быть fp или ap, ∘ может быть сложением или умножением, а imm может быть любым фиксированным элементом поля):
- имм
- [reg1+offop1][reg1+offop1]
- [reg0+offop0]∘[reg1+offop1][reg0+offop0]∘[reg1+offop1]
- [reg0+offop0]∘imm[reg0+offop0]∘imm
- [[reg0+offop0+offop1][[reg0+offop0+offop1]
Примечание 2. Деление и вычитание могут быть выражены как умножение и сложение с разным порядком операндов соответственно.
Инструкцию assert можно рассматривать как инструкцию присваивания, в которой значение одной стороны известно, а значение другой стороны неизвестно.
Например, [ap]=4[ap]=4 можно рассматривать как утверждение о том, что значение [ap] равно 4, или как установку присваивания [ap] на 4, в соответствии с контекст.
На рис. 4 показаны некоторые примеры инструкций «подтвердить равенство» и значения флагов для каждой инструкции:
Рисунок 4. Примеры инструкций Assert Equal
Интерпретировать инструкцию [fp + 1] = 5:
- инструкция утверждения => код операции = 4
- next_ap = ap => ap_update = 00 = 0
- next_pc = pc + размер_инструкции => pc_update = 000 = 0
- Для op0 и op1 нет add или mul => res_logic(res) = 00 = 0
- Существует непосредственное значение => op1_src(op1) = 001 = 1
- Адрес непосредственного значения указывает, что адреса являются смежными => off_op1 = 1
- Левая часть уравнения [fp + 1] => dst_reg(dst) = 1
- Левая часть уравнения [fp + 1] => off_dst = 1
- op0_reg/ off_op0 => начальное значение (1/-1) //Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
Комментарий условного и безусловного перехода
Инструкция jmp позволяет изменить значение программного счетчика pc.
Cairo поддерживает относительный переход (его операнд представляет собой смещение от текущего pcpc) и абсолютный переход, представленный ключевыми словами rel и abs соответственно.
Инструкция jmp может быть условной. Например, если значение блока памяти не равно 0, будет запущена инструкция jmp.
Синтаксис инструкции следующий:
```javascript
Безусловные переходы.
jmp абс <адрес>
jmp отн <смещение>
Условные переходы.
jmp rel <смещение> if
На рис. 5 показаны некоторые примеры инструкций jmp и соответствующие значения флагов для каждой инструкции:
Рисунок 5 Примеры инструкций перехода
Интерпретация инструкция jmp rel [ap +1] + [fp - 7]:
- инструкция jmp => код операции = 0
- next_ap = ap => ap_update = b00 = 0
- next_pc = pc + res=> pc_update = b010 = 2
- res = op0 + op1 => res_logic(res) = b01 = 1
- op1: [fp - 7] => op1_src(op1) = b010 = 2
- op1: [fp - 7] => off_op1 = -7
- op0: [ap + 1] => op0_src(op0) = 0
- op0: [ap + 1] => off_op0 = 1
- dst_reg/ off_dst => начальное значение (1/-1) ///Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
звонить и отзывать
Инструкции call и ret позволяют реализовать стек функций. Инструкция call обновляет регистры счетчика программы (pc) и указателя кадра (fp ).
Обновление счетчика программ аналогично инструкции jmp . Предыдущее значение fp записывается в [ap], чтобы позволить инструкции ret сбросить значение fp до значения перед вызовом; аналогичным образом возвращенный pcpc (адрес инструкции после инструкции call) записывается в [ap+1], чтобы позволить инструкции ret вернуться назад и продолжить выполнение кода после инструкции call. .
Поскольку были записаны две единицы памяти, ap увеличивается на 2, а fp устанавливается на новый ap.
Синтаксис инструкций следующий:
```javascript
позвонить в абс <адрес>
вызов rel <смещение>
рет
На рис. 6 показаны некоторые примеры инструкций call и ret и значения флагов, соответствующие каждой инструкции:
Рисунок 6 Примеры инструкций вызова и инструкций возврата
Интерпретировать вызов инструкции abs [fp + 4]:
- инструкция вызова => код операции = 0
- next_ap = ap => ap_update = b00 = 0
- next_pc = res => pc_update = b001 = 1
- res = op1 => res_logic(res) = b00 = 0
- op1: [fp + 4] => op1_src(op1) = b010 = 2
- op1: [fp + 4] => off_op1 = 4
- op0_reg/ off_op0 => начальное значение (0/1) ///Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
- dst_reg/ off_dst => начальное значение (0/0) ///Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
Продвижение ap
Инструкция ap +=
Комментарий
На рис. 7 показаны некоторые примеры инструкций ap и соответствующие значения флагов для каждой инструкции:
Рис. 7 Примеры продвижения инструкций ap
Интерпретировать инструкцию ap += 123:
- инструкция продвижения ap => код операции = 0
- next_ap=ap+res=>ap_update=b01=1
- next_pc = pc + размер_инструкции => pc_update = b000 = 0
- res = op1 => res_logic(res) = b00 = 0
- op1 = 123 => op1_src(op1) = b001 = 1
- op1 = 123 => off_op1 = 1
- op0_reg/ off_op0 => начальное значение (1/-1) ///Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
- dst_reg/ off_dst => начальное значение (1/-1) ///Поскольку эти флаги не используются в этой инструкции, заполняется значение по умолчанию.
Оригинал