- PVSM.RU - https://www.pvsm.ru -
Здравствуйте! В этой статье я расскажу какие шаги нужно пройти для создания простого процессора и окружения для него.
Для начала нужно определиться с тем, каким будет процессор. Важны такие параметры как:
Архитектуры процессоров можно разделить по размеру инструкций на 2 вида (на самом деле их больше, но другие варианты менее популярны):
Основное их отличие в том, что RISC процессоры имеют одинаковый размер инструкций. Их инструкции простые и выполняются сравнительно быстро, тогда как CISC процессоры могут иметь разный размер инструкций, некоторые из которых могут выполняться достаточно продолжительное время.
Я решил сделать RISC процессор во многом похожий на MIPS [6].
Я это сделал по целому ряду причин:
Вот основные характеристики моего процессора:
Register type(досл. Регистровый тип) выглядит вот так:

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

Инструкции этого типа оперируют с двумя регистрами и числом.
OP — это номер инструкции, которую нужно выполнить (или же для указания, что эта инструкция Register type).
R0, R1, R2 — это номера регистров, которые служат операндами для инструкции.
Func — это дополнительное поле, которое служит для указания вида Register type инструкций.
Imm — это поле куда записывается то значение, которое мы хотим явно предоставить инструкции в качестве операнда.
Полный список инструкций можно посмотреть в github репозитории [8].
Вот лишь пару из них:
nor r0, r1, r2
NOR это Register type инструкция, которая делает логическое ИЛИ НЕ на регистрах r1 и r2, после записывает результат в регистр r0.
Для того, чтобы использовать эту инструкцию нужно изменить поле OP на 0000 и поле Func на 0000000111 в двоичной системе счисления.
lw r0, n(r1)
LW это Immediate type инструкция, которая загружает значение памяти по адресу r1 + n в регистр r0.
Для того, чтобы использовать эту инструкцию в свою очередь нужно изменить поле OP на 0111, а в поле IMM записать число n.
После создания ISA можно приступить к написанию процессора.
Для этого нам нужно знание какого нибудь языка описания оборудования. Вот некоторые из них:
Я выбрал Verilog, т.к. программирование на нем было частью моего учебного курса в университете.
Для написания процессора нужно понимать логику его работы:
И так до бесконечности.
Получается нужно создать несколько модулей:
Разберем по отдельности каждый модуль.
Регистровый файл предоставляет доступ к регистрам. С его помощью нужно получать значения каких то регистров, или изменять их.
В моем случае у меня 64 регистра. В один из регистров записывается результат операции над двумя другими, так что мне нужно предоставить возможность изменять только один, а получать значения из двух других.
Декодер это тот блок, который отвечает за декодирование инструкций. Он указывает какие операции нужно выполнить АЛУ и другим блокам.
Например, инструкция addi должна сложить значение регистра $zero(Он всегда хранит 0) и 20 и положить результат в регистр $t0.
addi $t0, $zero, 20
На этом этапе декодер определяет, что эта инструкция:
И передает эти сведения следующим блокам.
После управление переходит в АЛУ. В нем обычно выполняются все математические, логические операции, а также операции сравнения чисел.
То есть, если рассмотреть ту же инструкцию addi, то на этом этапе происходит сложение 0 и 20.
По мимо вышеперечисленных блоков, процессор должен уметь:
Тут [12] и там [8] можно увидеть как это выглядит в коде.
После написания процессора нам нужна программа, которая бы преобразовывала текстовые команды в машинный код, чтобы не делать этого вручную. Поэтому нужно написать ассемблер.
Я решил реализовать его на языке программирования Си.
Так как мой процессор имеет RISC архитектуру, то для того, чтобы упростить себе жизнь, я решил спроектировать ассемблер так, чтобы в него можно было легко добавлять свои псевдоинструкции(комбинации из нескольких элементарных инструкций или из других псевдоинструкций).
Можно реализовать это с помощью структуры данных, хранящей в себе тип инструкции, ее формат, указатель на функцию, которая возвращает машинные коды инструкции, и ее название.
Обычная программа начинается с объявления сегмента.
Для нас достаточно двух сегментов .text — в котором будет храниться исходный код наших программ — и .data — в котором будет хранится наши данные и константы.
Инструкция может выглядеть вот так:
.text
jie $zero, $zero, $zero # Ветвление
addi $t1, $zero, 2 # $t1 = $zero + 2
lw $t1, 5($t2) # $t1 = *($t2 + 5)
syscall 0, $zero, $zero # syscall(0, 0, 0)
la $t1, label# $t1 = label
Сначала указывается название инструкции, потом операнды.
В .data же указываются объявления данных.
.data
.byte 23 # Константа размером 1 байт
.half 1337 # Константа размером 2 байта
.word 69000, 25000 # Константы размером 4 байта
.asciiz "Hello World!" # Константная нуль терминируемая строка (Си строка)
.ascii "12312009" # Константная строка (без терминатора)
.space 45 # Пропуск 45 байтов
Объявление должно начинаться с точки и названия типа данных, после же идут константы или аргументы.
Удобно парсить (сканировать) ассемблер файл в таком виде:
Для работы ассемблеру нужно проходить исходный файл 2 раза. В первый раз он считает по каким смещениям находятся ссылки (они служат для), они обычно выглядят вот так:
la $s4, loop # Загружаем адрес loop в s4
loop: # Ссылка!
mul $s2, $s2, $s1 # s2 = s2 * s1
addi $s1, $s1, -1 # s1 = s1 - 1
jil $s3, $s1, $s4 # если s3 < s1 то перейди на метку
А во второй проход можно уже и генерировать файл.
В дальнейшем, можно запускать выходной файл из ассемблера на нашем процессоре и оценивать результат.
Также готовый ассемблер можно использовать в Си компиляторе. Но это уже позже.
Ссылки:
Автор: i1i1
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/verilog/299859
Ссылки в тексте:
[1] Архитектура набора команд (ISA): https://ru.wikipedia.org/wiki/%D0%90%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0_%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4
[2] машинного слова: https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D0%BE%D0%B5_%D1%81%D0%BB%D0%BE%D0%B2%D0%BE
[3] регистров: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0%B0
[4] RISC: https://ru.wikipedia.org/wiki/RISC
[5] CISC: https://ru.wikipedia.org/wiki/CISC
[6] MIPS: https://ru.wikipedia.org/wiki/MIPS_(%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0)
[7] счетчик команд: https://ru.wikipedia.org/wiki/%D0%A1%D1%87%D1%91%D1%82%D1%87%D0%B8%D0%BA_%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4
[8] github репозитории: https://github.com/i1i1/bbmips
[9] Декодирование: https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BE%D0%B4%D0%B5%D1%80
[10] Регистровый файл: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80%D0%BE%D0%B2%D1%8B%D0%B9_%D1%84%D0%B0%D0%B9%D0%BB
[11] АЛУ: https://ru.wikipedia.org/wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BB%D0%BE%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE
[12] Тут: https://github.com/alexskp/MIPS-Core
[13] Источник: https://habr.com/post/430680/?utm_campaign=430680
Нажмите здесь для печати.