Uploaded by Mutruduz Channel

20-KB IV1 KR EVM Zosimov

advertisement
Министерство науки и высшего образования Российской Федерации
ФГБОУ ВО «Кубанский государственный технологический университет»
(ФГБОУ ВО «КубГТУ»)
Институт____КСИБ____________________________________________________
Кафедра Информатики и вычислительной техники__________________________
Направление подготовки 09.03.01 Информатика и вычислит. техника___________
Профиль ЭВМ, комплексы, системы и сети________________________________
(наименование профиля)
КУРСОВАЯ РАБОТА
по дисциплине________ЭВМ и периферийные устройства_____________________
(наименование дисциплины)
на тему: «Разработка программной эмуляции процессора. Вариант 5»
(тема курсовой работы)
Выполнил(-а) студент(-ка) Зосимов М.А.__ _____ курса __2_группы 20-КБ-ИВ1__
(фамилия, имя, отчество)
Допущена к защите ___________________
(дата)
Руководитель (нормоконтролер) работы _________________Атрощенко ВА..
(должность, подпись, дата)
Защищена _____________
Оценка ______________________
(дата)
Члены комиссии _________________________________________________
_________________________________________________
_________________________________________________
(должность, подпись, дата, расшифровка подписи)
Краснодар
2022г.
ФГБОУ ВО «Кубанский государственный технологический университет»
(ФГБОУ ВО «КубГТУ»)
Институт____КСИБ_________ ___________________________________________
Кафедра_Информатики и вычислительной техники__________________________
Направление подготовки 09.03.01Информатика и вычислит. техника___________
(код и наименование направления подготовки)
УТВЕРЖДАЮ
Зав. кафедрой ИВТ______ ____
профессор_______Атрощенко В.А.
______________________2022г.
ЗАДАНИЕ
на курсовую работу
Студенту(-ке) Зосимов М.А._________ курса__2___ группы_20-КБ-ИВ1______
Тема работы: «Разработка программной эмуляции процессора. Вариант 5»
(утверждена указанием директора института №___ от _____20__ г.)
План работы:
1._______________________________________________________________
2._______________________________________________________________
3._______________________________________________________________
Объем работы:
а) пояснительная записка _15-20____ с.
б) иллюстративная часть _10____ листов формата A4
Рекомендуемая литература:
1._______________________________________________________________
2._______________________________________________________________
Срок выполнения:
с «_____»___________ по «_____»__________20__г.
Срок защиты:
«____»________ 20__г.
Дата выдачи задания:
«_____»_______ 20__г.
Дата сдачи работы на кафедру:
«_____»_______ 20__г.
Руководитель работы
_______________________ Атрощенко В.А.
(должность, подпись)
Задание принял(-а) студент(-ка) ______________________ Зосимов М.А.
(подпись)
2
Реферат
Курсовая работа: 39 с., 3 рис., 2 табл., 5 источников, 2 прил.
ЦЕНТРАЛЬНЫЙ
ПРОЦЕССОР,
КОМАНДА,
АДРЕС,
НЕПОСРЕДСТВЕННАЯ АДРЕСАЦИЯ, РЕГИСТРОВАЯ АДРЕСАЦИЯ, ПРЯМАЯ
АДРЕСАЦИЯ, ПРЕВЫВАНИЕ, СЛОЖЕНИЕ, СДВИГ, ИНВЕРСИЯ
В данной курсовой работе рассматривается принцип работы процессора и его
практическая реализация с использованием языка описания аппаратуры Verilog
согласно заданному варианту.
Объект исследования – центральный процессор.
Цель работы – разработать программную реализацию заданного процессора,
используя язык описания аппаратуры.
Вариант задания:
3
Содержание
Введение 5
1. Устройство центрального процессора ........................................................ 6
1.1.
Память инструкций и счетчик команд...................................................... 7
1.2.
Память данных ............................................................................................ 7
1.3.
Файл регистров ........................................................................................... 7
2. Проектирование системы команд процессора. ......................................... 8
2.1
2.2
Команды ассемблера .................................................................................. 8
Команды типа R-type (register type) .......................................................... 9
2.3
Команды типа I-type (immediate type)..................................................... 10
2.4
Счетчик команд ......................................................................................... 13
2.5
Файл регистров ......................................................................................... 14
3. Выбор размера массива постоянной памяти. .......................................... 16
3.1
3.2
Память данных .......................................................................................... 16
Память инструкций ................................................................................... 17
4. Процесс обработки команд в процессоре. ................................................ 19
4.1
Шина данных и контроллер ..................................................................... 19
4.2
Подключение счетчика инструкций ....................................................... 20
4.2
4.3
Подключение файла регистров ............................................................... 20
Разбор команд (контроллер) .................................................................... 21
4.4
Выполнение команд (шина данных) ....................................................... 22
5. Верхний уровень проекта. ........................................................................... 27
Заключение ........................................................................................................... 29
Список используемой литературы .................................................................. 30
Приложение. Листинг ......................................................................................... 31
Приложение. Проверка на антиплагиат......................................................... 39
4
Введение
В курсовой работе по дисциплине «ЭВМ и периферийные устройства» целью
является освоение основных принципов, положенных в основу работы центральных
процессорных устройств (ЦПУ), и получение представления о принципах обработки
и выполнения команд процессором.
В современном мире трудно найти область техники, где не применялись бы
микропроцессоры.
В микропроцессорах - наиболее сложных микроэлектронных устройствах воплощены самые передовые достижения инженерной мысли. В условиях
свойственной данной отрасли производства жесткой конкуренции и огромных
капиталовложений выпуск каждой новой модели микропроцессора, так или иначе,
связан с очередным научным, конструкторским, технологическим прорывом.
В курсовой работе основные задачи
 освоить основные элементы
архитектуры процессора на примере процессора MIPS, понять язык ассемблер и
конвертацию его в машинный код процессора, углубить знания в области
дисциплины «ЭВМ и периферийные устройства.
5
1.
Устройство центрального процессора
Центральный процессор – это основной рабочий компонент компьютера,
основная задача которого выполнение программы, находящейся в основной памяти.
Процессор вызывает команды из памяти, определяет их тип, затем выполняет
их одну за другой.
Компоненты процессора соединены шиной, которая представляет собой набор
параллельных дорожек печатной платы процессора и по которым передаются
адреса, данные и сигналы управления.
Шины могут быть внешними (связывающими процессор с памятью и
устройствами ввода-вывода) и внутренними.
Архитектура процессора в целом определяется тремя основными блоками:
 арифметико-логическим устройством (АЛУ);
 памятью (набором регистров);
 блоком управления по командам ассемблера.
Для работы процессора необходимы память инструкций (instruction memory) и
память данных (data memory), а также адрес текущей выполняемой инструкции,
которую контролирует счетчик программы (program counter – pc). Общая схема
работы процессора представлена на рисунке 1.
Рис. 1. Общая схема работы процессора
На каждый такт процессора из памяти инструкций берется инструкция с
адресом pc, далее в зависимости от типа инструкции и ее параметров данные
берутся из файла регистров и/или из памяти данных. С этими данными
выполняются соответствующие действия, и они отправляются обратно в файл
регистров и/или в память данных и меняют значение счетчика команд рс. Таким
6
образом, текущее состояние процессора определено значениями, записанными в
файл регистров, память данных и счетчик команд, а логика перехода между
различными
состояниями
процессора
определена командами, выбранными из памяти инструкций.
1.1. Память инструкций и счетчик команд
Память инструкций (instruction memory)  адресуемая область памяти,
хранящая последовательность команд, которые одну за другой выполняет
процессор. Адресуемая память означает, что каждое 32-битовое слово (word) в этой
области, состоящее из четырех байтов, имеет свой порядковый номер, т.е. адрес.
Для 32-битовой архитектуры длина адреса определяется также 32 битами (4 байта).
Следовательно, память инструкций может содержать максимально 232 адресов
памяти.
Счетчик программы (program counter  pc) содержит 32-битный адрес
команды, которую выполняет процессор в текущем такте работы. Поскольку каждая
команда занимает 4 байта, то корректными значениями счетчика программы могут
быть только адреса, которые кратны 4. Такая адресация слов позволяет обеспечить
адресацию составляющих эти слова байтов.
1.2. Память данных
Память данных (data memory) – это адресуемая область памяти, хранящая
произвольные данные, которыми может оперировать процессор во время
выполнения программы. Аналогично памяти инструкций длина адреса для 32битной архитектуры составляет 32 бита, т.е. программа может адресовать максимум
232 адресов памяти данных. Байты также логически организованы в слова по 4 байта
на слово (для 32-битной архитектуры). Тогда на физическом уровне объем памяти
для 32 битных слов составит массив размером 232 х 32 бит.
1.3. Файл регистров
Файл регистров (register file)  часть памяти процессора MIPS32 с массивом
32x32 бита (разрядность может быть другой). Каждый регистр файла регистров
содержит 32 бита информации и может иметь свой индивидуальный адрес от 0 до
31. Таким образом, количество адресов файла регистров равно 2 5 = 32, а длина
адреса каждого регистра составляет 5 бит информации. При этом для удобства
регистры разбиты на группы, а также имеют символьные имена и конкретные
назначения, что отражено в таблице 1.
Память регистров (в отличие от памяти данных) доступна процессорному
блоку непосредственно и располагается с ним на одном кристалле, поэтому
операции с регистрами выполняются быстро, но их количество ограничено. Исходя
из этих ограничений, работа с регистрами обычно осуществляется по следующей
схеме: данные загружаются в один или несколько регистров из памяти инструкций
или внешней памяти, процессор выполняет с ними необходимые операции и
отправляет результат обратно во внешнюю память или в файл регистров, но никогда
в память инструкций.
7
Название
$0
$at
$v0-$v1
$a0-$a3
Номер
0
1
23
47
Назначение
константа 0 (только чтение)
временная ассемблера
возвращаемые значения процедур
аргументы процедур
$t0-$t7
815
временные переменные
$s0-$s7
$t8-$t9
$k0-$k1
$gp
1623
2425
2627
28
сохраненные переменные
временные переменные
временные значения операционной системы
глобальный указатель
$sp
29
указатель стека
$fp
30
указатель стекового кадра (stack frame)
31
адрес возврата процедуры
Таблица 1 - Регистры процессора MIPS32
В программе, написанной в данной курсовой работы предусмотрено
использование следующих регистров: $0, $t0-$t7, $s0-$s7. Адреса этих регистров
приведены в таблице 2. При необходимости двоичные значения для остальных
регистров легко вычислить самостоятельно, применив недостающие комбинации
пятиразрядного кода.
$ra
Регистр
Адрес
$0
00000
$s0
10000
$s1
10001
$s2
10010
$s3
10011
$s4
10100
$s5
10101
$s6
10110
Таблица 2 - Двоичные индексы используемых регистров процессора MIPS32 в
работе
2.
Проектирование системы команд процессора.
2.1 Команды ассемблера
Язык ассемблер  это самый низкоуровневый язык программирования. По
сути, это представленный в удобном для чтения человеком вид машинного кода, с
которым работает процессор. Каждая команда ассемблера имеет однозначное
8
представление в машинном коде, и наоборот  любую команду, записанную
машинным кодом, можно легко перевести в читаемый вид на ассемблере.
Для каждого процессора язык ассемблера свой. В ассемблере набор команд
подобран таким образом, чтобы все их многообразие можно было однозначно
закодировать всеми возможными комбинациями значений доступных 32 бит (для
32-битного процессора) как код машинной команды. При этом с помощью этого
набора можно было эффективно выполнять программы, транслируемые в
ассемблер компиляторами высокоуровневых языков программирования (типа С
или С++). Выполнить этот подбор  задача архитекторов процессора.
Набор и структура команд, которые использованы при реализации
процессора, выбраны и реализованы таким образом как посчитали возможным
инженеры и архитекторы MIPS Technologies. Команды с архитектур других
процессоров могут решать эту же задачу другим образом. Для каждой из
архитектур есть свои причины использования команд ассемблера: инженерные,
технологические, коммерческие и другие.
Реализация команд 32-битного процессора MIPS, которые будут
использоваться в работе:
 add (сложение);
 lw (загрузка значения из памяти данных);
 sw (сохранение значения в память данных);
 addi (сложение с константой);
 subi (условный переход);
Команды ассемблера процессора MIPS разбиты на группы по типу
используемых ими параметров операндов. Машинный код каждой команды вместе с
адресами операнд занимает 32 бита (для 32-битной архитектуры MIPS).
2.2 Команды типа R-type (register type)
Команды типа R-type имеют 3 операнда, все операнды  это адреса регистров:
два регистра  источники данных для выполнения команды, один регистр –
результат выполненной команды.
Регистровая команда add
Эта команда складывает значения двух регистров и помещает результат в
третий регистр.
Синтаксис на ассемблере выглядит так:
add rd, rs, rt
rd, rs, rt  адреса регистров. Команда add берет значения из регистров rs и rt,
адреса которых указаны в команде; вычисляет сумму этих регистров и записывает
результат в регистр rd по указанному адресу в команде.
В архитектуре процессора MIPS эта команда имеет вид:
add $s0, $s1, $s2
Для архитектуры процессора это будет выглядеть как операция $s0=$s1+$s2,
вычисляющая сумму значений, хранящихся в регистрах $s1 и $s2, и результат,
записанный в регистр $s0.
9
В машинном коде команды типа R-type разбиты по битам на следующие
поля:
[ op]
[ rs]
[ rt]
[ rd]
[shamt]
[ funct]
(6 бит)
(5 бит) (5 бит) (5 бит) (5 бит)
(6 бит)
Поля op и funct – это поля операций, причем поле op (opcode) = 0 для всех
операций для R-type, а поле funct (function): для команды add=32, для команды
sub=34.
Поля rs, rt, rd  это адреса регистров операндов соответствующих команд.
Поля rs, rt – поля адресов источников операндов (rs – источник 1, rt –
источник 2). Обозначение выбрано следующим образом: символ r – register, символ
s – source (источник), а символ t следует в алфавите после символа s.
Поле rd – поле результата или назначение (destination), поэтому в rd символ
d.
Поле
shamt
–
определяющее
сдвиг
битов
в
адресах
(amount of shift) = 0 для всех операций типа R-type.
2.3 Команды типа I-type (immediate type)
Команды типа I-type в отличие от команд типа R-type умеют работать с
числовыми константами (immediates – незамедлительные значения, т.е. их не нужно
ниоткуда получать), которые встроены непосредственно в код команды.
Из рассматриваемого списка команд к таковым относятся:
lw, sw, addi, beq
Команда lw (load word  загрузить слово) загружает значение слова из памяти
данных в регистр.
Синтаксис на ассемблере:
lw rt, imm (rb),
где rt  адрес регистра назначения, imm – константа, формирующая адрес
загружаемого значения из памяти, rb  базовый адрес регистра, содержащего
значение, относительно которого осуществляется сдвиг на значение константы для
формируемого адреса загрузки из памяти.
В архитектуре процессора MIPS эта команда имеет вид:
lw $s0, 4 ($0),
которая вычисляет адрес загружаемого значения в регистр s0, взятого из
памяти по адресу (4+0=4), где 4 – это константа imm, а 0 – это адрес базового
регистра (в нашем случае в качестве базового регистра используется нулевой
регистр, который имеет адрес 0).
Команда sw (store word  сохранить слово) сохраняет значение из регистра в
память данных за счет записи этого значения.
Синтаксис на ассемблере:
sw rt, imm (rb)
rt  адрес регистра-источника откуда будет записываться информация, imm
– константа, формирующая адрес сохранения значения в памяти, rb – адрес
базового регистра, относительно которого формируется адрес сохранения в
памяти.
10
В архитектуре процессора MIPS команда сохранения слова имеет вид:
sw $s0, 4 ($0),
она вычисляет адрес сохранения значения из регистра s0 в память процессора
по адресу (4+0=4), где 4 это – константа imm, а 0 – это адрес базового регистра (в
качестве базового регистра используется нулевой регистр, который всегда имеет
адрес 0).
Команда addi (add immediate  сложить с константой) складывает значение
регистра с константой и записывает результат в регистр.
Синтаксис на ассемблере:
addi rt, rs, imm
rt  адрес регистра-назначения, rs  адрес регистра источника, содержащего
первое слагаемое, imm  константа – второе слагаемое.
Команда addi должна взять значения из регистра rs, вычислить сумму
данных регистра rs и числа imm (rs+imm) и записать этот результат в регистр rt.
В архитектуре процессора MIPS эта команда сложения с константой имеет
вид:
addi $s0, $s1, 4,
она вычисляет следующую сумму: $s1+4 = $s0 и записывает результат в
регистр $s0.
Команда beq (branch if equal  ветвление, если равно) осуществляет
условный переход по значению адреса счетчика программы в указанное место при
выполнении определенного условия.
В обычной ситуации процессор выполняет программу строка за строкой.
Этот механизм реализован при помощи счетчика программы (program counter – pc):
на каждый такт процессор выбирает из памяти инструкций команду, адрес которой
хранит счетчик программы, после ее выполнения счетчик программы увеличивает
значение адреса на одну команду и на следующем такте процессор выберет для
выполнения команду, которая в памяти инструкций следует за текущей. Так может
продолжаться до тех пор, пока не закончится память инструкций.
Команды перехода (в данном случае условного) позволяют нарушить
последовательный ход программы и на следующем такте процессора выполнять
команду, не следующую за текущей, а выбрать команду в указанном адресе памяти
инструкций. Для этого достаточно установить нужное значение в счетчик
программы (pc).
При помощи команды условного перехода beq можно организовывать циклы
и блоки проверки условий.
Синтаксис такой команды на ассемблере:
beq rs, rt, target
rs, rt  адреса регистров, значения которых сравнивает команда;
target – (цель) адрес перехода относительно текущей инструкции.
Адрес перехода target задается при помощи специальных меток 
символьных имен, которые можно назначить на любую строку в ассемблерной
программе.
11
В архитектуре процессора MIPS команда условногоперехода может иметь
вид:
label: addi $s0, $0, 4
beq $s0, $s1, label,
она сравнит значения регистров s0 и s1 и передаст управление инструкции,
адрес которой в памяти инструкций помечен меткой label, если значения равны (в
данном примере это команда на одну строку выше команды перехода beq); если
значения не равны, то программа продолжит последовательное выполнение команд
со следующей строки программы.
В машинном коде эти инструкции выглядят следующим образом:
[ op]
[ rs]
[ rt]
[ imm]
(6 бит) (5 бит) (5 бит) (16 бит)
op  код операции, который имеет следующие значения: для lw=35, для
sw=43, для addi=8, для beq=4.
rs, rt  адреса регистров-операндов,
target (цель) – метка, устанавливаемая на адрес перехода.
Система команд процессора приведена в таблице 3.
Таблица 3
Код
операции (6b)
Система команд процессора
Мнем
Содержание
оника
100000
add
сложение
100011
lw
загрузка значения из памяти данных
101011
sw
сохранение значения в память данных
001000
addi
сложение с константой
000100
beq
условный переход
Пример реализации эмуляции процессора MIPS32 в среде Quartus II
Разработка процессора MIPS осуществляется на языке Verilog, в котором
использованы понятия процессора, языка ассемблера и системы реализованных
команд. Язык SystemVerilog обеспечивает описания аппаратуры процессора.
Проект процессора будет состоять из пяти модулей, написанных на языке
SystemVerilog, Сначала требуется создать эти модули, а затем перейти к его
верхнему схемному уровню процессора. В приложении Е приведены основные
операторы языка SystemVerilog и приоритет их выполнения. Приложение Ж
содержит примеры обозначения чисел (констант) на языке SystemVerilog.
Основные модули, которые определяют текущее состояние системы это 
счетчик команд (program counter), файл регистров (register file), память инструкций
(instruction memory) и память данных (data memory).
12
Далее эти модули соединяются между собой центральной логикой работы
процессора, в рамках которой реализованы механизмы разбора потока
ассемблерных команд из памяти инструкций и логика их исполнения с помощью
модуля шины данных и контроллера (datapath_and_controller). Каждая команда в
момент выполнения будет влиять на один или несколько перечисленных модулей
одновременно, изменяя счетчик программы, файл регистров и память данных.
На каждый такт периодического сигнала Clock процессор выбирает ровно
одну команду из памяти инструкций и в этом же такте исполняет ее.
Проект будет состоять из следующих программных модулей:
 pc.v – счетчик команд;
 regfile.v – файл регистров;
 datapath_and_controller.v  модуль основной логики (шина данных и
контроллер);
 datamem.v  память данных;
 instrmem.v – память инструкций;
 Processor.bdf – схема верхнего уровня, осуществляющая соединение всех
модулей.
2.4 Счетчик команд
Программный модуль счетчика команд (program counter) позволяет установить
следующее значение счетчика программы на каждый такт синхросигнала.
На вход этого элемента поступает 1 бит тактового сигнала clk (Clock) и 32битное следующее значение счетчика программы pc' (program counter next 
pc_next). По переднему фронту сигнала clk 32-битное текущее значение счетчика
команд pc (program counter) меняется на следующее значение выхода счетчика
команд pc'. Такое изменение счетчика команд pc происходит на каждом такте
синхронизирующего сигнала clk.
Код программы на языке Verilog реализует именно эту логику работы
счетчика команд. В коде программы /* означают комментарии программы,
поясняющие действия её команд и операндов.
pc.v
/**
* Счетчик программы - переход на следующее значение на каждый такт.
* @param clk - тактовый сигнал clock
* @param pc_next - следующее значение для счетчика программы (program
counter)
* @param pc – текуще значение счетчика программы (program counter)
*/
module pc
(input clk,input [31:0] pc_next, output reg [31:0] pc);
always @(posedge clk) /*всегда выполнять по переднему фронту
pc <= pc_next;/* счетчику программ присваивать новое значение
endmodule
13
2.5 Файл регистров
Программный модуль файл регистров (register file) хранит значения
внутренних регистров процессора и позволяет получать 32-битное значение
регистра по пятибитному адресу, а также делать запись 32-битного значения в
регистр файла регистров по пятибитному адресу.
Модуль может обеспечивать чтение значений до двух регистров
одновременно, а запись выполнять только в один регистр за один такт сигнала
Clock.
Весь код модуля выглядит следующим образом:
regfile.v
module regfile(input clk,
/* Чтение 2х регистров */
input [4:0] ra1, input [4:0] ra2,
output [31:0] rd1, output [31:0] rd2,
/* Запись в регистр */
input we, input [4:0] wa, input [31:0] wd);
reg [31:0] rf [31:0];
always @(posedge clk)
if(we) rf[wa] <= wd;/*если we=1 записать данные wd по адресу регистра wa/*
assign rd1 = ra1 ? rf[ra1] : 0;/* reg[0] всегда ноль*/
assign rd2 = ra2 ? rf[ra2] : 0;/* reg[0] всегда ноль*/
endmodule
В программном коде выше описаны следующие параметры:
Для операций чтения
Входы:
ra1  адрес чтения (read address) регистра-источника 1  5 бит;
ra2  адрес чтения (read address) регистра-источника 2  5 бит.
Выходы:
rd1  считываемые данные (read data) регистра-источника 1  32 бит;
rd2  считываемые данные (read data) регистра-источника 2  32 бит.
Для операций записи
Входы:
clk  тактовый сигнал clock  запись в регистр-назначение осуществляется на
каждый тактовый сигнал при включенном флаге write enabled (we =1);
we  флаг разрешения записи (write enabled);
wa  адрес записи (write address) регистра-назначения  5 бит;
wd  записываемые данные для (write data) регистра-назначения  32 бит.
14
Для хранения данных файла регистра используется массив размером 32 x 32
бит (адресов 25 = 32 бита, размер слов 32 бита) и команда программы reg [31:0] rf
[31:0].
Операция записи данных wd (write data) в регистр по адресу wa (write
address) на каждый такт сигнала clk (clock) производится, если флаг we (write
enabled) равен 1 по следующим командам ранее приведенной программы.
always @(posedge clk)
if(we) rf[wa] <= wd;
Операция чтения из двух регистров производится по адресам ra1 и ra2.
Значения считываемых из регистров данных появляются на выходах модуля rd1 и
rd2 в момент присвоения значений адресов входам ra1 и ra2 с появлением
тактового сигнала clock. Для регистра по адресу 0 всегда возвращается значение 0
(в регистре $0 всегда находится константа ноль), что показано во фрагменте
приведенной ранее программы:
assign rd1 = ra1 ? rf[ra1] : 0; // reg[0] всегда ноль
assign rd2 = ra2 ? rf[ra2] : 0; // reg[0] всегда ноль
15
3. Выбор размера массива постоянной памяти.
3.1 Память данных
Программный модуль памяти данных (data memory)  RAM (random access
memory) представляет собой запоминающее устройство с произвольным доступом
на
чтение
и
запись.
Память
адресуется
32
32-битным указателем 2
бит = 4096 Мегабайт. Организована память
32-битными словами по 4 байта каждый, загрузка и сохранение осуществляется
также словами, состоящими из 4 байт. Таким образом, полный объем памяти
составляет 1 073 741 824 бит в доступном адресном пространстве.
datamem.v
/**
* Память данных.
* @param clk - clock
* @param we - флаг разрешения записи (write enabled)
* @param addr - адрес доступа на чтение/запись (address)
* @param wd - записываемые данные (write data)
* @param rd - считанные данные (read data)
*/
module datamem_plain (input clk,input we, input [31:0] addr,
input [31:0] wd, output [31:0] rd);
// массив памяти данных
reg [31:0] RAM [63:0];
// запись данных в RAM если флаг we (write enabled) равен '1'
always @(posedge clk)
if(we) RAM[addr[31:2]] <= wd;
// чтение данных из RAM
// выравнивание слов по байтам (word aligned) - деление кода дреса addr на 4//
(просто отбросить 2 последних бита)
assign rd = RAM[addr[31:2]];
endmodule
Массив 32-битных слов с N-адресами – это доступная физическая память.
N = 1 073 741 824 – количество бит, выделенных для памяти; но такого объема
памяти для реальных физических устройств ПЛИС не хватит. Поэтому можно
взять вентилей столько, сколько потребуется программе. В данном случае примем
N = 63, поэтому фрагмент ранее приведенной программы имеет вид:
// массив памяти данных
reg [31:0] RAM [63:0];
Чтение и запись данных в память RAM происходит по каждому такту
импульса clock:
16
 запись при условии, если флаг we (write enabled) равен 1;
 чтение происходит по факту присвоения нового значения адреса
входу с addr.
При работе с адресом происходит выравнивание памяти по словам (word
aligned): для обращения к 4-байтовому слову по адресу байта адрес addr
требуется поделить на 4 (т.е. сдвинуть код адреса вправо на 2 бита, что
соответствует выражению «Отбросить 2 последних бита»).
Фрагмент кода ранее приведенной программы имеет вид
// запись данных в RAM если флаг we (write enabled) равен '1' always
@(posedge clk)
if(we)RAM[addr[31:2]] <= wd;
// чтение данных из RAM
assign rd = RAM[addr[31:2]];
3.2 Память инструкций
Программный модуль памяти инструкций (instruction memory)  ROM (readonly memory) – это память, доступная только для чтения. По логике адресации
полная аналогия с памятью данных: 32-битный адрес, 32-битные слова с
выравниванием по 4 байта. Главное отличие от модуля памяти данных  это
отсутствие интерфейса записи.
Как и в случае с модулем памяти данных, код программы будет считываться
непосредственно из модуля Verilog. Он представлен в виде двоичных констант с
заранее
определенными
адресами:
одна
инструкция  одно 4-х байтное слово. Для этого предварительно код программы на
ассемблере необходимо вручную перевести в двоичный вид по разбиению, которое
было приведено в разделе настоящих методических указаний 6.2.
Например, для такой ассемблерной программы, состоящей из двух строк
addi $s0, $0, b0000000011111111
sw $s0, 0xf000 ($0)
модуль памяти инструкций на языке Verilog будет выглядеть следующим
образом:
instrmem.v
module instrmem (
/* адрес чтения инструкции */
input [31:0] addr,
/* значение инструкции */
output reg [31:0] instr);
always @ (addr)
case (addr)
32'h00000000: instr <= 32'b100011_00000_10000_0000000000000000; // lw $s0, 0 ($0)
17
32'h00000004: instr <= 32'b001000_10000_10001_0000000000010010; // addi $s1, $s0, 18
32'h00000008: instr <= 32'b100111_10001_10010_0000000000001001; //subi $s2, $s1, 9
32'h0000000C: instr <= 32'b101011_00000_10010_0000000000000100; //sw $s2,4($0)
32'h00000010: instr <= 32'b000010_00000000000000000000000000; //j 0
default: instr <= 0;
endcase
endmodule
//lw слова 0
//прибавить к нему 18 addi
//вычесть из него 9 subi
//sw по адресу 4
//j перейти в начало
//lw $s0, 0($0)
//addi $s1, $s0, 18
//subi $s2, $s1, 9
//sw $s2,4($0)
//j 0
Определяется:
– для входа в инструкции:
addr – 32-битный адрес инструкции, необходимый для подключения к
текущему значению счетчика программы (program counter).
– для выхода (получения инструкции):
instr – 32-битное значение инструкции, которое должно быть прочитано в
двоичном представлении ассемблерной команды, на которую указывает адрес addr
(или счетчик программы), в соответствии с полями команды её представления в
типовом процессоре MIPS.
18
4. Процесс обработки команд в процессоре.
4.1 Шина данных и контроллер
Программный модуль шина данных и контроллер (datapath and controller)
осуществляет разбор потока команд ассемблерной программы, который приходит к
нему из памяти инструкций, и производит выполнение каждой команды на один
такт синхросигнала процессора clk.
В данный модуль входят два основных логических блока:
 контроллер (controller)  дешифровка двоичных инструкций (машинных
кодов) процессора, которые поступают в модуль из памяти инструкций;
– шина данных (datapath)  логика передачи данных между блоками
процессора: между файлом регистров и памятью данных, установка счетчика
программы и т.д. в зависимости от значения текущей инструкции.
Программный код модуля имеет следующий вид:
/** * Шина данных и контроллер (datapath and contoller) - переключает
счетчик программы
* (program counter), содержит значения файла регистров (register file) и
обрабатывает инструкции.
* @param clk - тактовый сигнал clock
* @param pc - счетчик программы (program counter)
* @param instr - значение текущей инструкции
* @param dmem_we - флаг разрешения записи (we - write enabled) в память
данных (dmem - data memory)
* @param dmem_addr - адрес (addr) доступа к памяти данных для
чтения/записи
* @param dmem_wd - данные для записи в память данных (wd - write data)
* @param dmem_rd - данные чтения из памяти данных (rd - read data)
*/
module datapath and controller (input clk,
/* Счетчик программы и текущая инструкция */
output [31:0] pc, input [31:0] instr)
/* Работа с памятью данных */
output reg dmem we,
output reg[31:0]dmem_addr,
output reg[31:0]dmem_wd,
input [31:0] dmem_rd);
Код модуля, приведенный выше, на каждый такт процессора выполняет
следующие действия:
1. Разбор текущего значения 32-битной инструкции на составляющие поля
команды.
2. В зависимости от типа инструкции и значений ее внутренних полей
производится вычисление значений, которые должны быть отправлены в файл
19
регистров, память данных или счетчик программы по факту выполнения текущей
команды.
3. В момент такта процессора clock происходит запись значений,
подготовленных на предыдущем этапе, уже непосредственно в файл регистров,
память данных и счетчик инструкций.
4. Этапы повторяются, пока не закончатся инструкции в памяти инструкций.
Для вычисления результатов работы каждой команды и записи вычисленных
значений по назначению на каждый такт синхросигнала clock используются всё те
же два блока always  always @(*) и always @(posedge clk). Первый блок always
@(*) вычисляет значения результата работы инструкций и находится
непосредственно в текущем модуле шина данных и контроллер (datapath and
controller), а второй always @(posedge clk) в трех экземплярах содержится в
модулях счетчик программы (pc  program counter), регистровом файле (register
file) и памяти данных (data memory).
Параметры программы:
clk  тактовый сигнал clock;
pc – значение счетчика программы (program counter);
instr  значение текущей инструкции;
dmem we  флаг разрешения записи (we  write enabled) в память данных
(dmem  data memory);
dmem addr  адрес (addr) доступа к памяти данных для чтения/записи;
dmem wd  данные для записи в память данных (wd – write data);
dmem rd  данные чтения из памяти данных (rd – read data).
4.2 Подключение счетчика инструкций
Для созданного модуля процессора счетчик инструкций (program counter)
подключаемые параметры clk (clock) и pc (program counter) поступают извне, для
манипуляции с адресом следующей инструкции pc_next подсоединяется
локальный регистр. Все, что в него попадет между тактами процессора в процессе
исполнения команды, будет записано в выход pc на каждый тактовый сигнал clk.
Если записано новое значение с адресом инструкции в регистр pc_next в
процессе выполнения текущей команды, то на следующий такт сигнала clk на
входе instr появится 32-битное значение инструкции, расположенной в памяти
инструкций по этому новому адресу.
Фрагмент программы для этого случая имеет вид:
reg [31:0] pc_next;
// Program counter
pc pcount (clk, pc_next, pc);
4.3 Подключение файла регистров
Второй созданный модуль  файл регистров (register file). Все управляющие
входы/выходы продублированы в виде локальных переменных, которым
соответствую отдельные соединительные дорожки и регистры. Пояснения к
20
механизмам чтения-записи в целом аналогичны описанному выше счетчику
инструкций.
Чтение. Как только регистр файла регистров rf_ra1 (register file read address
1) получает новое значение в виде пяти битного адреса происходит чтение.
Дорожка rf_rd1 (register file read data 1) получает 32-битное значение, которое
хранится в файле регистров по указанному адресу rf_ra1.
Аналогично происходит запись в регистр файла регистров fr_ra2 (register file
read address 2), для этого автоматически инициируется получение значения на 32битной дорожке rf_rd2 (register file read data 2). Запись синхронизирована с
тактовым сигналом процессора clk (clock). Устанавка rf_we (register file write
enable) в 1 обеспечит разрешение записи. В rf_wa (register file write address)
устанавливается пятибитный адрес регистра, в который будет произведена запись,
в rf_wd (register file write data) – 32-битное значение для записи: на следующий
такт сигнала clk значение rf_wd отправится в регистр по адресу rf_wa. Если флаг
rf_we установлен в 0, то запись не производится.
Программа для файла регистров имеет вид:
/ Файл регистров /
reg [4:0] rf_ra1, rf_ra2;
wire [31:0] rf_rd1, rf_rd2;
reg rf_we;
reg [4:0] rf_wa;
reg [31:0] rf_wd;
regfile rf(clk, rf_ra1, rf_ra2, rf_rd1, rf_rd2, rf_we, rf_wa, rf_wd);
4.4 Разбор команд (контроллер)
Разбор инструкций на составляющие поля происходит при помощи создания
на каждое поле переменной wire (перевод «провод», в нашем понимании дорожки
печатной платы) с нужной разрядностью. Этой переменной при помощи
оператора assign присваивается соответствующий диапазон битов внутри 32битной инструкции instr.
Список инструкций, названия и разрядность полей, а также их расположение
внутри инструкции были рассмотрены в разделе 6.2 данных методических
указаний.
Первое поле, общее для всех типов инструкций, имеет код операции op
(opcode), который занимает 6 старших бит.
Фрагмент программы имеет следующий вид:
// Инструкции
wire [5:0] instr_op;
assign instr_op = instr[31:26]; // 6 бит
Для инструкций типа R-type нужны поля: rs, rt, rd, shamt, funct.
Неиспользуемые поля можно закомментировать.
21
// R-тип
wire [4:0] instr_rtype_rs;
wire [4:0] instr_rtype_rt;
wire [4:0] instr_rtype_rd;
//wire [4:0] instr_rtype_shamt;
wire [5:0] instr_rtype_funct;
assign instr_rtype_rs = instr[25:21]; // 5 бит
assign instr_rtype_rt = instr[20:16]; // 5 бит
assign instr_rtype_rd = instr[15:11]; // 5 бит
//assign instr_rtype_shamt = instr[10:6]; // 5 бит – в этом типе не используется
assign instr_rtype_funct = instr[5:0]; // 6 бит
Для инструкций типа I-type необходимы поля: rs, rt, imm.
// I-тип
wire [4:0] instr_itype_rs;
wire [4:0] instr_itype_rt;
wire [15:0] instr_itype_imm;
assign instr_itype_rs = instr[25:21]; // 5 бит
assign instr_itype_rt = instr[20:16]; // 5 бит
assign instr_itype_imm = instr[15:0]; // 16 бит
4.5 Выполнение команд (шина данных)
Для удобства работы создается несколько констант с значениями полей op
(код операции  общий для всех) и funct (для команд типа
R-type) и для всех команд, которые будет уметь исполнять проектируемый
процессор: lw, sw, addi, beq, add.
Фрагмент программы для поля ОР для инструкций имеет вид:
parameter INSTR_OP_LW = 6'b100011;
parameter INSTR_OP_SW = 6'b101011;
parameter INSTR_OP_ADDI = 6'b001000;
parameter INSTR_OP_J = 6'b000010;
parameter INSTR_OP_RTYPE = 6'b000000;
parameter INSTR_RTYPE_FUNCT_ADD = 6'b100000;
parameter INSTR_RTYPE_FUNCT_SUB = 6'b100010;
parameter INSTR_OP_SUBI = 6'b100111;
Программа в первом always в стартовой секции устанавливает значения по
умолчанию для всех команд: перевод счетчика инструкций на следующую команду
(+4 бита к текущему значению), запрещается запись в файл регистров и память
данных, остальные значения сбрасываются в 0, и открывается ветвление case по
полю instr_op (код операции).
Код программы имеет вид:
22
always @(*)
begin
// по умолчанию перевод счетчика на следующую инструкцию
pc_next = pc + 4;
// запрет записи в файл регистров и память данных
rf_we = 0;
dmem_we = 0;
// сброс остальных значений в 0 по умолчанию
rf_ra1 = 0;
rf_ra2 = 0;
rf_wa = 0;
rf_wd = 0;
dmem_addr = 0;
dmem_wd = 0;
case(instr_op)
Далее в каждой из веток case произойдет реализация соответствующей
команды.
Например, команда типа R-type: add.
Поле opcode для всех команд R-type равно нулю, а окончательный тип
команды определяет поле funct, поэтому сразу же открываем по этому полю
второе вложенное ветвление case, чтобы можно было отличить add от других
команд этого типа, инструкция программы имеет вид:
INSTR_OP_RTYPE:
case(instr_rtype_funct)
Ниже приведен код команды add для реализации команды сложения
значений двух регистров:
INSTR_RTYPE_FUNCT_ADD:
begin
// add $s2, $s0, $s2
// $s2 = $s1 + $s0
// rs=$s0, rt=$s2, rd=$s2
// rf_rd1 получит значение регистра rs
rf_ra1 = instr_rtype_rs;
// rf_rd2 получит значение регистра rt
rf_ra2 = instr_rtype_rt;
// записать значение в регистр rd на следующий такт clock
rf_wa = instr_rtype_rd;
rf_wd = rf_rd1 + rf_rd2;
rf_we = 1;
end
23
Команда add работает следующим образом:
add $s2, $s1, $s0
Результат работы команды:
$s2 = $s1 + $s0 в регистр $s2 попадает сумма значений двух регистров $s1 и
$s0
Поля операции:
rs= адрес $s0, rt= адрес $s1, rd= адрес $s2
Выполнение инструкции программы будет происходить следующим образом.
Считывается значение первого операнда: устанавливается адрес чтения регистра
rf_ra1 в значение поля операции rs (5 бит). Сразу после этого 32-битное значение
регистра появится в переменной rf_rd1.
rf_ra1 = instr_rtype_rs;
Аналогично считывается значение второго операнда: устанавливается адрес
чтения регистра rf_ra2 в значение поля операции rt (5 бит). После этого 32-битное
значение регистра появится в переменной rf_rd2.
rf_ra2 = instr_rtype_rt;
Значения операндов-источников rs и rt считаны  на них указывают
переменные rf_rd1 и rf_rd2 соответственно. Теперь нужно записать результат в
файл регистров по адресу, указанному в третьем операнде-назначении rd.
Значение адреса записи регистра rf_wa устанавливается в значение поля
операции rd.
rf_wa = instr_rtype_rd;
Значение для записи попадает в регистр rf_wd (32 бит)  это сумма значений
двух регистров rs и rt, т.к. выполняется операция сложения.
rf_wd = rf_rd1 + rf_rd2;
Осталось включить флаг разрешения записи в файл регистров rf_we, таким
образом, новое значение с суммой регистров rs и rt отправится в регистр rd на
следующий такт сигнала clock.
rf_we = 1;
Аналогично реализуются и другие команды типа R. После написания кода
для команд типа R необходимо закрыть вложенный case для команд этого типа:
endcase
После завершения этого типа команды можно продолжить работу с
командами всех остальных типов.
Команды lw загружают слово (32 бит) из памяти данных (0 и 4) в регистры
$s0 и $s1.По спецификации команды адрес слова в памяти данных является суммой
значений регистра-операнда rs и константы imm.
Выполнение команды происходит следующим образом:
1. Получаем значение регистра rs в rf_rd1, присвоив адрес регистра
переменной rf_ra1.
2. Вычисляем адрес как rf_rd1 + imm (значение регистра
rs + константа imm) и одновременно получаем значение слова в памяти данных в
переменной dmem_rd, присвоив значение вычисленного адреса переменной
24
dmem_addr (механизм аналогичен описанному механизму получения значения из
регистра).
3. Осуществляем запись значения dmem_rd в регистр rt на следующий такт
clock уже известным способом.
INSTR_OP_LW:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// прочитать значение из памяти,
// dmem_rd немедленно получит значение по адресу dmem_addr
dmem_addr = rf_rd1 + instr_itype_imm;
// записать значение в регистр rt на следующий такт clock
rf_wa = instr_itype_rt;
rf_wd = dmem_rd;
rf_we = 1;
end
Команда sw сохраняет слово (32 бит) из указанного регистра в память
данных. По спецификации команды адрес слова в памяти данных является суммой
значений регистра-операнда rs и константы imm. Механизм записи в память
данных очевидно аналогичен механизму записи в файл регистров  указываем
адрес, значение, включаем флаг записи и ждем следующий такт clock.
INSTR_OP_SW:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// rf_rd2 немедленно получит значение регистра rt
rf_ra2 = instr_itype_rt;
// записать значение в память данных на следующий такт clock
dmem_addr = rf_rd1 + instr_itype_imm;
dmem_wd = rf_rd2;
dmem_we = 1;
end
Команда addi складывает значение регистра и константы, результат
сохраняет в регистр.
INSTR_OP_ADDI:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// записать значение в регистр rt на следующий такт clock
25
rf_wa = instr_itype_rt;
rf_wd = rf_rd1 + instr_itype_imm;
rf_we = 1;
end
Команда subi вычитание константы из содержимого регистра, размещение
результата в регистре назначения Rd.
INSTR_OP_SUBI:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// записать значение в регистр rt на следующий такт clock
rf_wa = instr_itype_rt;
rf_wd = rf_rd1 - instr_itype_imm;
rf_we = 1;
end
Команда j выполняется переход по адресу внутри всего объема (4М слов)
памяти программ.
INSTR_OP_J:
begin
pc_next = instr_jtype_addr;
end
endcase
26
5.
Верхний уровень проекта.
После написания всех нужных модулей на языке Verilog создаём файл с
расширением .bdf – это и будет верхний схемный уровень в иерархии проекта. На
этой схеме разместим все созданные вами модули (рис. 3) и соединим их
графически.
С помощью квадратных скобок можно показать разрядность шины. Если
требуется соединим шины разной разрядности соответствующим образом их
подписав, как указано у блока regfile : instr[25..21], instr[20..16], instr[15..11].
После этого проводим симуляцию полученной схемы. Она отображен на
рисунке 2.
Рис. 2. Пример симуляции проекта
27
Рис. 3. Верхний уровень проекта
28
Заключение
В данной курсовой работе был разработан процессор MIPS с заданными
параметрами с использованием языка аппаратуры на языке Verilog, в котором
использованы понятия процессора, языка ассемблера и системы реализованных
команд. Язык SystemVerilog обеспечивает описания аппаратуры процессора.
Работа состоит из пяти модулей. В первую очередь были созданы эти
модули, а затем работа перешла к верхнему схемному уровню процессора.
Команда в момент выполнения влияет на один или несколько модулей
одновременно, изменяя счетчик программы, файл регистров и память данных.
Процессор выбирает ровно одну команду из памяти инструкций на каждый такт
периодического сигнала Clock и в этом же такте исполняет ее.
В конце была проведена симуляция процессора, на которой видно время,
потраченное на выполнение каждой команды.
29
Список используемой литературы
1. Акулов О.А., Медведев Н.В. Информатика. Базовый курс: учебник для
студентов вузов. – М.: Омега-Л, 2013. – 574 с.
2. Гук М. Аппаратные средства IBM PC. Энциклопедия.  СПб.: Питер, 2006. –
1072 с.
3. Таненбаум Э., Остин Т. Архитектура компьютера.  6 изд.  СПб.: Питер,
2013. – 816 с.
4. Харрис Д.М. Цифровая схемотехника и архитектура компьютера / Д.М.
Харрис, С.Л. Харрис; пер. с англ. Imagination Technologies. – М.: ДМК Пресс,
2018.  792 с.
30
Приложение. Листинг
module regfile(input clk,
/* Чтение 2х регистров */
input [4:0] ra1, input [4:0] ra2,
output [31:0] rd1, output [31:0] rd2,
/* Запись в регистр */
input we, input [4:0] wa, input [31:0] wd);
reg [31:0] rf [31:0];
always @(posedge clk)
if(we) rf[wa] <= wd;
assign rd1 = ra1 ? rf[ra1] : 0; // reg[0] всегда ноль
assign rd2 = ra2 ? rf[ra2] : 0; // reg[0] всегда ноль
endmodule
/**
*
Счетчик программы - переход на следующее значение на каждый такт.
*
* @param clk - тактовый сигнал clock
*
* @param pc_next - следующее значение для счетчика программы (program counter)
* @param pc - счетчик программы (program counter)
*/
module pc(input clk,
input [31:0] pc_next, output reg [31:0] pc);
always @(posedge clk)
pc <= pc_next;
endmodule
31
/** * Шина данных и контроллер (datapath and contoller) - переключает счетчик программы
* (program counter), содержит файла регистров (register file) и обрабатывает инструкции.
* @param clk - тактовый сигнал clock
* @param pc - счетчик программы (program counter)
* @param instr - значение текущей инструкции
* @param dmem_we - флаг разрешения записи (we - write enabled) в память данных (dmem - data
memory)
* @param dmem_addr - адрес (addr) доступа к памяти данных для чтения/записи
* @param dmem_wd - данные для записи в память данных (wd - write data)
* @param dmem_rd - данные чтения из памяти данных (rd - read data)
*/
module datapath_and_controller(input clk,
output [31:0] pc, input [31:0] instr,
output reg dmem_we,
output reg [31:0] dmem_addr,
output reg [31:0] dmem_wd,
input [31:0] dmem_rd);
reg [31:0] pc_next;
pc pcount(clk, pc_next, pc);
reg [4:0] rf_ra1, rf_ra2;
wire [31:0] rf_rd1, rf_rd2;
reg rf_we;
reg [4:0] rf_wa;
reg [31:0] rf_wd;
regfile rf(clk,
rf_ra1, rf_ra2, rf_rd1, rf_rd2,
rf_we, rf_wa, rf_wd);
wire [5:0] instr_op;
assign instr_op = instr[31:26];
32
// R-тип
wire [4:0] instr_rtype_rs;
wire [4:0] instr_rtype_rt;
wire [4:0] instr_rtype_rd;
//wire [4:0] instr_rtype_shamt;
wire [5:0] instr_rtype_funct;
assign instr_rtype_rs = instr[25:21]; // 5 бит
assign instr_rtype_rt = instr[20:16]; // 5 бит
assign instr_rtype_rd = instr[15:11]; // 5 бит
//assign instr_rtype_shamt = instr[10:6]; // 5 бит – в этом типе не используется
assign instr_rtype_funct = instr[5:0]; // 6 бит
//I-тип
wire [4:0] instr_itype_rt;
wire [4:0] instr_itype_rs;
wire [15:0] instr_itype_imm;
assign instr_itype_rs = instr[25:21]; // 5 бит
assign instr_itype_rt = instr[20:16]; // 5 бит
assign instr_itype_imm = instr[15:0]; // 16 бит
//J-тип
wire [25:0] instr_jtype_addr;
assign instr_jtype_addr = instr [25:0];
parameter INSTR_OP_LW = 6'b100011;
parameter INSTR_OP_SW = 6'b101011;
parameter INSTR_OP_ADDI = 6'b001000;
parameter INSTR_OP_J = 6'b000010;
parameter INSTR_OP_RTYPE = 6'b000000;
parameter INSTR_RTYPE_FUNCT_ADD = 6'b100000;
33
parameter INSTR_RTYPE_FUNCT_SUB = 6'b100010;
parameter INSTR_OP_SUBI = 6'b100111;
always @(*)
begin
// по умолчанию перевод счетчика на следующую инструкцию
pc_next = pc + 4;
// запрет записи в файл регистров и память данных
rf_we = 0;
dmem_we = 0;
// сброс остальных значений в 0 по умолчанию
rf_ra1 = 0;
rf_ra2 = 0;
rf_wa = 0;
rf_wd = 0;
dmem_addr = 0;
dmem_wd = 0;
case(instr_op)
INSTR_OP_RTYPE:
case(instr_rtype_funct)
INSTR_RTYPE_FUNCT_ADD:
begin
// add $s0, $s1, $s2
// $s0 = $s1 + $s2
// rs=$s1, rt=$s2, rd=$s0
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_rtype_rs;
34
// rf_rd2 немедленно получит значение регистра rt
rf_ra2 = instr_rtype_rt;
// записать значение в регистр rd на следующий такт clock
rf_wa = instr_rtype_rd;
rf_wd = rf_rd1 + rf_rd2;
rf_we = 1;
end
endcase
INSTR_OP_LW:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// прочитать значение из памяти,
// dmem_rd немедленно получит значение по адресу dmem_addr
dmem_addr = rf_rd1 + instr_itype_imm;
// записать значение в регистр rt на следующий такт clock
rf_wa = instr_itype_rt;
rf_wd = dmem_rd;
rf_we = 1;
end
INSTR_OP_SW:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// rf_rd2 немедленно получит значение регистра rt
rf_ra2 = instr_itype_rt;
// записать значение в память данных на следующий такт clock
35
dmem_addr = rf_rd1 + instr_itype_imm;
dmem_wd = rf_rd2;
dmem_we = 1;
end
INSTR_OP_ADDI:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// записать значение в регистр rt на следующий такт clock
rf_wa = instr_itype_rt;
rf_wd = rf_rd1 + instr_itype_imm;
rf_we = 1;
end
INSTR_OP_SUBI:
begin
// rf_rd1 немедленно получит значение регистра rs
rf_ra1 = instr_itype_rs;
// записать значение в регистр rt на следующий такт clock
rf_wa = instr_itype_rt;
rf_wd = rf_rd1 - instr_itype_imm;
rf_we = 1;
end
INSTR_OP_J:
begin
pc_next = instr_jtype_addr;
end
36
endcase
end
endmodule
/**
* Память данных.
* @param clk - clock
* @param we - флаг разрешения записи (write enabled)
* @param addr - адрес доступа на чтение/запись (address)
* @param wd - записываемые данные (write data)
* @param rd - считанные данные (read data)
*/
module datamem_plain (input clk,
input we, input [31:0] addr,
input [31:0] wd, output [31:0] rd);
// массив памяти данных
reg [31:0] RAM [63:0];
// запись данных в RAM если флаг we (write enabled) равен '1'
always @(posedge clk)
if(we) RAM[addr[31:2]] <= wd;
// чтение данных из RAM
// выравнивание по словам (word aligned) - поделить адрес addr на 4
// (просто отбросить 2 последних бита)
assign rd = RAM[addr[31:2]];
endmodule
module instrmem (
/* адрес чтения инструкции */
input [31:0] addr,
37
/* значение инструкции */
output reg [31:0] instr);
always @ (addr)
case (addr)
32'h00000000: instr <= 32'b100011_00000_10000_0000000000000000; // lw $s0, 0 ($0)
32'h00000004: instr <= 32'b001000_10000_10001_0000000000010010; // addi $s1, $s0, 18
32'h00000008: instr <= 32'b100111_10001_10010_0000000000001001; //subi $s2, $s1, 9
32'h0000000C: instr <= 32'b101011_00000_10010_0000000000000100; //sw $s2,4($0)
32'h00000010: instr <= 32'b000010_00000000000000000000000000; //j 0
default: instr <= 0;
endcase
endmodule
//lw слова 0
//прибавить к нему 18 addi
//вычесть из него 9 subi
//sw по адресу 4
//j перейти в начало
//lw $s0, 0($0)
//addi $s1, $s0, 18
//subi $s2, $s1, 9
//sw $s2,4($0)
//j 0
38
Приложение. Проверка на антиплагиат
39
Download