26 KiB
Написание программы под процессор CYBERcobra
Чтобы максимально "прочувствовать" принцип работы созданного вами процессора, вам необходимо написать один из вариантов программ. Вариант выдается преподавателем.
Порядок выполнения задания следующий:
-
В первую очередь необходимо ознакомиться с заданием и изучить пример, приведенный в конце задания. Если возникли вопросы по заданию или примеру, свяжитесь с преподавателем. Чем лучше вы понимаете что от вас ожидают, тем проще будет выполнить задание.
-
Составьте алгоритм работы программы (буквально возьмите листочек, и нарисуйте блок-схему). Прежде чем вы погрузитесь в увлекательное приключение с ноликами и единицами, вам нужно составить четкую карту вашего путешествия.
-
Проверьте вашу блок-схему на данных из примера. Если все сошлось, проверьте вашу блок-схему на других данных. Не забывайте про краевые случаи (отрицательные числа, деление на ноль, переполнения и прочее — для каждого задания они могут быть разными).
-
После того как вы убедились в работоспособности алгоритма на всех возможных данных, наступает время претворить его в виде двоичной программы.
-
Программа описывается в текстовом файле. Для удобства был написан специальный конвертер, который будет принимать на вход текстовый файл с комментариями и двоичным кодом, разделенным пробелами, а на выход выдавать текстовый файл, которым можно будет проинициализировать память инструкций. Подробнее о конвертере смотрите в разделе cyberconverter. Пример текстового файла, который сможет принять конвертер:
//J B WS ALUop RA1 RA2 const WA 0 0 00 11111111111111111111111 00001 // загрузить константу -1 регистр 1 0 0 10 00000000000000000000000 00010 // загрузить значение с входа sw_i в регистр 2 0 0 00 00000000000000000000001 00011 // загрузить константу 1 регистр 3 0 0 01 00000 00001 00011 00000000 00001 // сложить регистр 1 с регистром 3 и поместить результат в регистр 1 0 1 00 11110 00001 00010 11111111 00000 // если значение в регистре 1 меньше значения в регистре 2, возврат на 1 инструкцию назад 1 0 00 00000 00001 00000 00000000 00000 // бесконечное повторение этой инструкции с выводом на out_o значения в регистре 1
-
При реализации условных переходов следует иметь в виду пару правил:
-
Блок ветвления (аналог
if/else
) состоит из двух наборов инструкций:- инструкции блока
if
(если условие выполнилось) - инструкции блока
else
(если условие не выполнилось)
При этом сразу за инструкцией ветвления описываются инструкции блока
else
(т.к. в случае не выполнения условия перехода,PC
перейдет к следующей инструкции).
Для того, чтобы после выполнения инструкций блокаelse
не начались исполняться инструкции блокаif
, в конце блока этих инструкций необходимо добавить безусловный переход на инструкцию, следующую за инструкциями блокаif
. - инструкции блока
-
Если вы реализуете не ветвление (аналог блока
if/else
), а только проверку условия для выполнения какого-то блока инструкций (аналог блокаif
без блокаelse
), вы должны помнить, что блокelse
все равно есть, просто в этом блоке нет инструкций. Однако вы, как и в прошлом правиле, должны добавить безусловный переход на инструкцию, следующую за инструкциями блокаif
.
Этого можно избежать, если инвертировать ваше условие. В этом случае, если ваше инвертированное условие выполнится, вы сможете сразу пропустить нужное количество инструкций и начать исполнять инструкцию за пределами вашего блокаif
. Если инвертированное условие не выполнится (т.е. выполнится исходное условие),PC
перейдет к следующей инструкции, где и будут находиться ваши инструкции блокаif
.
Звучит достаточно запутанно, поэтому давайте рассмотрим пару примеров. Сначала мы запишем нашу идею на языке Си, а затем перенесем её в двоичный код под архитектуру CYBERcobra:if(reg[1]==reg[5]) { reg[2] = 10; reg[3] = 15; } else { reg[2] = 7; goto if_end; // Поскольку в памяти программы блок else будет идти // сразу за инструкцией условного перехода, необходимо // добавить в конце инструкцию безусловного перехода, // чтобы не начать исполнять инструкции блока if. } if_end:
Мы хотим проверить на равенство значения в регистровом файле по адресам
1
и5
. Если это так, записать значения10
и15
по адресам2
и3
соответственно. В противном случае, записать значение7
по адресу2
.
Это можно реализовать следующей двоичной программой://J B WS ALUop RA1 RA2 const WA 0 1 00 11000 00001 00101 00000011 00000 // Если регистры 1 и 5 равны, // перемести PC на 3 инструкции вперед // (перешагни через две // инструкции блока else) //--------------------------------------- // блок else //--------------------------------------- 0 0 00 00000000000000000000111 00010 // reg[2] = 7 1 0 00 00000 00000 00000 00000011 00000 // goto if_end //--------------------------------------- //--------------------------------------- // блок if //--------------------------------------- 0 0 00 00000000000000000001010 00010 // reg[2] = 10 0 0 00 00000000000000000001111 00011 // reg[3] = 15 //--------------------------------------- 0 0 00 00000000000000000000000 00000 // некая инструкция с меткой if_end // куда будет перемещен PC после // выполнения блока else
Рассмотрим второй пример, где нет блока
else
:if(reg[1] == reg[5]) { reg[2] = 10; reg[3] = 15; }
Как упоминалось ранее, можно реализовать этот условный переход по той же схеме (тогда пример программы на Си примет вид):
if(reg[1] == reg[5]) { reg[2] = 10; reg[3] = 15; } else { goto if_end; } if_end:
А можно инвертировать условие:
if(reg[1] != reg[5]) { } else { reg[2] = 10; reg[3] = 15; }
В этом случае, нет нужды делать безусловный переход на инструкцию, следующую за инструкциями блока
if
, т.к. там нет никаких инструкций.
Такое условие можно реализовать следующей двоичной программой://J B WS ALUop RA1 RA2 const WA 0 1 00 11001 00010 00101 00000011 00000 // Если регистры 2 и 5 НЕ РАВНЫ, // перемести PC на 3 инструкции вперед // (перешагни через две // инструкции блока else) //--------------------------------------- // блок else //--------------------------------------- 0 0 00 00000000000000000001010 00010 // reg[2] = 7 0 0 00 00000000000000000001111 00011 // reg[3] = 15 //---------------------------------------
-
-
В двоичном программировании, реализация циклов лучше всего делается аналогом
do while
в Си (если вы уверены, что первая итерация цикла гарантированно пройдет условие выхода из цикла). В этом случае, вы сперва описываете тело цикла, а затем через условный переход возвращаетесь обратно к началу тела цикла. Если условие не выполнилось, вы автоматически выйдете из цикла. -
Для того, чтобы в конце выполнения программы было легко увидеть результат выполнения, в конец программы необходимо добавить инструкцию безусловного перехода, поле
const
которой равно нулю. В этом случае, будет выполнятьсяPC=PC+0
что приведет к повторению этой инструкции снова и снова. При этом в полеRA1
необходимо указать адрес регистра, где хранится результат. На временной диаграмме это отобразится так, что в какой-то момент все сигналы процессора "замрут", а на выходеout_o
окажется результат, вычисленный вашей программой. -
После того, как вы написали программу, её необходимо проверить. Для этого сперва необходимо преобразовать её к формату, принимаемому памятью инструкций с помощью программы
cyberconverter
. При необходимости, заменить данные в файле, инициализирующем память инструкций актуальными данными. -
Если ваша программа использует данные с внешних устройств, нужно выставить проверяемое вами значение в модуле
testbench
на входsw_i
в месте подключения модуляCYBERcobra
. -
Проверка работы программы осуществляется аналогично проверке модуля
CYBERcobra
— вы достаете внутренние сигналы модуля, и смотрите за поведением сигналов:PC
,read_data
памяти инструкций,flag
АЛУ, содержимым регистрового файла. Проверяете, что в конце на выходеout_o
размещено корректное значение.
cyberconverter
cyberconverter — это программа, которая преобразует текстовый файл с инструкциями архитектуры CYBERcobra в текстовый файл, который сможет принять память инструкций.
cyberconverter может обрабатывать файлы, содержащие комментарии (начинающиеся с //
), пробелы и пустые строки, а так же наборы символов 0
и 1
. Комментарии, пробелы и пустые строки удаляются, после чего оставшиеся строки из 32 нулей и единиц конвертируются в шестнадцатиричные значения и записываются в выходной файл.
cyberconverter принимает до двух аргументов. Порядок запуска следующий:
-
Вызов справки:
cyberconverter -h cyberconverter --help
-
Преобразование программы, записанной в файле
test.txt
, с записью результата в файлprogram.txt
:cyberconverter test.txt program.txt
-
Если не указан второй аргумент, результат будет записан в файл:
<имя_исходного_файла>_converted.<расширение исходного файла>
:cyberconverter test.txt
Результат будет записан в файл
test_converted.txt
. -
Если программа будет запущена без аргументов, то исходным файлом будет считаться файл
program.txt
.
В случае отсутствия исходного файла, наличия неподдерживаемых символов или неверной длины инструкции будет выведено сообщение об ошибке.
Индивидуальные задания
В приведенных ниже заданиях под a
будет подразумеваться некоторое число, заданное в программе (например в программе прописано a=10
), под sw_i
— вход с внешних устройств. "Вывести в out_o
" — означает, что в конце программы необходимо реализовать бесконечный цикл, с указанием в RA1
адреса регистра, хранящего результат (см. пункт 8 раздела "Написание программы под процессор CYBERcobra").
В случае, если задание используется для написания программы на ассемблере, sw_i
будет обозначать еще одно число, заданное в программе (как и a
), а под "Вывести в out_o
" — запись результата в регистр x10
(в назначение этого регистра входит возврат результата функции) в конце выполнения программы.
-
Вычислить циклический сдвиг вправо
a >> sw_i
.
Пример:a = 0...01011
,sw_i = 0...010
.
Результат вычислений:out_o = 110...010
. -
Вычислить
a - sw_i
без использования операции вычитания.
Пример:sw_i = 0...011
,a = 0...0100
.
Результат вычислений:out_o = 0...001
. -
Вычислить циклический сдвиг влево
a << sw_i
.
Пример:a = 10...01011
,sw_i = 0...10
.
Результат вычислений:out_o = 0...0101110
. -
Поменять местами
[7:0]
и[15:8]
биты числаsw_i
. Вывести результат вout_o
.
Пример:sw_i = 0...010100000_1110010
.
Результат вычислений:out_o = 0...011100101_10100000
. -
Вычислить приблизительное значение длины вектора
(a;sw_i)
. Вычисляется какmax + min/2
, гдеmax
иmin
— это большее и меньшее из чиселa
иsw_i
соответственно.
Пример:a = 0...011
,sw_i = 0...0100
.
Результат вычислений:out_o = 0...0101
.
-
Вычислить
a * sw_i
посредством суммыsw_i
значенийa
. Вывести результат вout_o
.
Пример:a = 5
,sw_i = 4
.5 * 4 == 5 + 5 + 5 + 5 = 20
.
Результат вычислений:out_o = 0...010100
. -
Если
sw_i[1:0] == 00
, то вout_o
выводитсяa
, еслиsw_i[1:0] == 01
, тоb
, еслиsw_i[1:0] == 10
, тоc
, еслиsw_i[1:0] == 11
, тоd
.
Пример:a = 0...00
,b = 0...010
,c = 0...011
,d = 0...001
,sw_i[1:0] = 01
.
Результат вычислений:out_o = 0...010
. -
Посчитать длину окружности при заданном радиусе
sw_i
, считая, чтоpi = 3
. Вывести результат вout_o
.
Пример:sw_i = 0...010
.
Результат вычислений:out_o = 0...01100
. -
Если
sw_i
является степенью двойки, то вывестиout_o = 0...01
, в противном случае,out_o = 0...0
.
Пример 1:sw_i = 0...0100
. Результат вычислений:out_o = 0...01
.
Пример 2:sw_i = 0...0110
. Результат вычислений:out_o = 0...00
. -
Найти количество нулей в двоичном представлении числа
sw_i
. Вывести результат вout_o
.
Пример:sw_i = 1...10110_0010
.
Результат вычислений:out_o = 0...0101
. -
Найти наибольший двоичный разряд числа
sw_i
, значение которого равно1
. Если такого нет, вывести32
. Вывести результат вout_o
.
Пример:sw_i = 0...0110
.
Результат вычислений:out_o = 0...010
. -
Сформировать число, состоящее из чётных двоичных разрядов числа
sw_i
. Вывести результат вout_o
.
Пример:sw_i = 0...011_1011_1000
.
Результат вычисленийout_o = 0...01_0100
. -
Найти количество единиц в двоичном представлении числа
sw_i
(обрати внимание,sw_i
– знаковое число). Вывести результат вout_o
.
Пример:sw_i = 0...0101_0110
.
Результат вычислений:out_o = 0...0100
. -
Найти количество двоичных разрядов, в которых различаются числа
sw_i
иa
. Вывести результат вout_o
.
Пример:sw_i = 0...0110
,a = 0...01110
.
Результат вычислений:out_o = 0...01
. -
Вывести в
out_o
подряд все единицы входного числаsw_i
.
Пример:sw_i = 0...01011011011
.
Результат вычислений:out_o
=0...01111111
. -
Вывести в
out_o
значение выражения:out = (k*a + b) + c
, гдеk
— этоsw_i[15:12]
,a
— этоsw_i[11:8]
,b
— этоsw_i[7:4]
,c
— этоsw_i[3:0]
.
Пример:sw_i = 0...0001_0000_0100_0011
.
Результат вычислений:out_o = 0...01010
.
-
Найти остаток от деления
sw_i
наa
. Вывести результат вout_o
.
Пример:sw_i = 0...0101
,a = 0...010
.
Результат вычислений:out_o = 0...01
. -
Найти и вывести в
out_o
количество вхожденийa[2:0]
вsw_i
без пересечений.
Пример:a[2:0] = 010
,sw_i = 0...01101_0101
.
Результат вычислений:out_o = 0...01
. -
Определить, сколько раз встречается
11
в двоичном представленииsw_i
без пересечений. Вывести результат вout_o
.
Пример:sw_i = 0...01110
.
Результат вычислений:out_o = 0...01
. -
Вывести в
out_o
результат целочисленного деленияa/sw_i
.
Пример:sw_i = 0...010
,a = 0...0111
.
Результат вычислений:out_o = 0...011
. -
Вывести в
out_o
суммуsw_i[3:0]
+sw_i[7:4]
+sw_i[11:8]
+sw_i[15:12]
.
Пример:sw_i[15:0] = 0001_0010_0011_0000
.
Результат вычислений:out_o = 0...0110
. -
В числе
sw_i
заменить справа-налево каждое00
на11
. Вывести результат вout_o
.
Пример:sw_i = 1...101000
.
Результат вычислений:out_o = 1...101011
.
-
Поменять местами чётные биты числа
sw_i
с нечётными битами этого числа (то есть соседние биты поменять местами). Вывести результат вout_o
.
Пример:sw_i = 0...01010_0111
.
Результат вычислений:out_o = 0...0101_1011
. -
Инвертировать первые
sw_i
бит числаa
. Вывести результат вout_o
.
Пример:sw_i = 0...011
,a = 0...01010_0011
.
Результат вычислений:out_o = 0...01010_0100
. -
Вывести n-ый член последовательности Фибоначчи Fn. n =
sw_i
. Вывести результат вout_o
.
Пример:sw_i = 0...0100
.
Результат вычислений:out_o = 0...010
. -
Поменять в числе
a
разрядыi = sw_i[4:0]
иj = sw_i[9:5]
. Вывести результат вout_o
.
Пример:a = 0...01001
,sw_i[9:0] = 00000_00001
. Значит, в числеа
необходимо поменять местамиа[0]
иa[1]
.
Результат вычислений:out_o = 0...01010
.
- Вычислить
a * sw_i
с использованием операций сложений и сдвига ("в столбик"). Вывести результат вout_o
.
Пример:a = 0...01011
,sw_i[9:0] = 0...01110
.
Результат вычислений:out_o = 0...010011010
.
1011 (11 в двоичном виде)
x 1110 (14 в двоичном виде)
======
0000 (это 1011 x 0)
1011 (это 1011 x 1, сдвинутое на 1 влево)
1011 (это 1011 x 1, сдвинутое на 2 влево)
+ 1011 (это 1011 x 1, сдвинутое на 2 влево)
=========
10011010 (154 в двоичном виде)
- Вывести в
out_o
n-ый член арифметической прогрессии aN, гдеa1 = a
,d = sw_i[15:8]
,n = sw_i[7:0]
(d и n неотрицательные).
Пример:sw_i[15:8] = 0000_0010
,sw_i[7:0] = 0000_0011
,a = 0...01
.
Результат вычислений:out_o = 0...0101
.
- Удалить все вхождения
sw_i[2:0]
изa
со сдвигом вправо (заполняя удаленные области).
Пример:a = 0...010011010
,sw_i[2:0] = 101
.
Результат вычислений:out_o = 0...010010