Лабораторная работа №7 "Тракт данных"

Микроархитектуру можно разделить на две части: тракт данных и устройство управления. По тракту данных перемещаются данные (из памяти инструкций, регистрового файла, АЛУ, памяти данных, мультиплексоров), а устройство управления (в нашем случае — декодер инструкций) получает текущую инструкцию из тракта и в ответ говорит ему как именно её выполнить, то есть управляет тем, как эти данные будут через проходить тракт данных.

Цель

Описать на языке SystemVerilog процессор с архитектурой RISC-V, реализовав его тракт данных, используя разработанные ранее блоки, и подключив к нему устройство управления. Итогом текущей лабораторной работы станет процессор RISC-V, который пока что сможет работать с памятью данных лишь посредством 32-битных слов (то есть БЕЗ инструкций, связанных с байтами и полусловами: lh, lhu, lb, lbu, sh, sb).

Ход работы

  1. Изучить микроархитектурную реализацию однотактного процессора RISC-V (без поддержки команд загрузки/сохранения байт/полуслов)
  2. Реализовать тракт данных с подключенным к нему устройством управления(#задание)
  3. Подготовить программу по индивидуальному заданию и загрузить ее в память инструкций
  4. Сравнить результат работы процессора на модели в Vivado и в симуляторе программы ассемблера

Микроархитектура RISC-V

processor_core

Рассмотрим микроархитектуру процессорного ядра processor_core. Данный модуль обладает следующим прототипом и микроархитектурой:

module processor_core (
  input  logic        clk_i,
  input  logic        rst_i,

  input  logic        stall_i,
  input  logic [31:0] instr_i,
  input  logic [31:0] mem_rd_i,

  output logic [31:0] instr_addr_o,
  output logic [31:0] mem_addr_o,
  output logic [ 2:0] mem_size_o,
  output logic        mem_req_o,
  output logic        mem_we_o,
  output logic [31:0] mem_wd_o
);

endmodule

../../.pic/Labs/lab_07_dp/fig_01.drawio.svg

Рисунок 1. Микроархитектура ядра процессора RISC-V.

Предложенная микроархитектура имеет схожую структуру c процессором CYBERcobra 3000 Pro 2.0 из ЛР№4, с некоторыми изменениями.

В первую очередь изменились входы и выходы процессора:

  • память инструкций вынесена наружу процессора, таким образом, у процессора появляются входы и выходы: instr_addr_o и instr_i;
  • помимо прочего, у модуля появились сигналы интерфейса памяти данных, реализованной в ЛР№6:
    • mem_addr_o — адрес внешней памяти;
    • mem_req_o — запрос на обращение во внешнюю память;
    • mem_size_o — размер данных при обращении в память;
    • mem_we_o — сигнал разрешения записи во внешнюю память;
    • mem_wd_o — данные для записи во внешнюю память;
    • mem_rd_i — считанные из внешней памяти данные; Эти сигналы используются при выполнении инструкций загрузки (сохранения) информации из (в) памяти данных.
  • еще у процессора появился вход stall_i, приостанавливающий обновление программного счётчика.

Кроме того, в данной микроархитектуре используется пять различных видов констант (соответствующих определенным типам инструкций).

Константы I, U, S используются для вычисления адресов и значений. Поэтому все эти константы должны быть подключены к АЛУ. А значит теперь, для выбора значения для операндов требуются мультиплексоры, определяющие, что именно будет подаваться на АЛУ.

Обратите внимание на константу imm_U. В отличие от всех остальных констант, она не знакорасширяется, вместо этого к ней "приклеивается" справа 12 нулевых бит.

Константы B и J используются для условного и безусловного перехода (в киберкобре для этого использовалась одна константа offset).

Программный счётчик (PC) теперь также изменяется более сложным образом. Поскольку появился еще один вид безусловного перехода (jalr), программный счётчик может не просто увеличиться на значение константы из инструкции, но и получить совершенно новое значение в виде суммы константы и значения из регистрового файла (см. на самый левый мультиплексор рис. 1). Обратите внимание, что младший бит этой суммы должен быть обнулен — таково требование спецификации [1, стр. 28].

Поскольку обращение во внешнюю память требует времени, необходимо приостанавливать программный счётчик, чтобы до конца обращения в память не начались исполняться последующие инструкции. Для этого у программного счётчика появился управляющий сигнал stall_i. Программный счётчик может меняться только когда этот сигнал равен нулю (иными словами, инверсия этого сигнала является сигналом enable для регистра PC).

processor_system

После реализации процессорного ядра, к нему необходимо подключить память. Это происходит в модуле processor_system.

module processor_system(
  input  logic        clk_i,
  input  logic        rst_i
);

endmodule

../../.pic/Labs/lab_07_dp/fig_02.drawio.svg

Рисунок 2. Микроархитектура процессорной системы.

Обратите внимание на регистр stall. Этот регистр и будет управлять разрешением на запись в программный счётчик PC. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счётчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например чип DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра stall_i единицу пока идет обращение в память.

Задание

Реализовать ядро процессора processor_core архитектуры RISC-V по предложенной микроархитектуре. Подключить к нему память инструкций и память данных в модуле processor_system. Проверить работу процессора с помощью программы, написанной на ассемблере RISC-V по индивидуальному заданию, которое использовалось для написания программы для процессора архитектуры CYBERcobra.

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

00:  addi  x1,  x0, 0x75С
04:  addi  x2,  x0, 0x8A7
08:  add   x3,  x1, x2
0C:  and   x4,  x1, x2
10:  sub   x5,  x4, x3
14:  mul   x6,  x3, x4    // неподдерживаемая инструкция
18:  jal   x15, 0x00050   // прыжок на адрес 0x68
1C:  jalr  x15, 0x0(x6)
20:  slli  x7,  x5, 31
24:  srai  x8,  x7, 1
28:  srli  x9,  x8, 29
2C:  lui   x10, 0xfadec
30:  addi  x10, x10,-1346
34:  sw    x10, 0x0(x4)
38:  sh    x10, 0x6(x4)
3C:  sb    x10, 0xb(x4)
40:  lw    x11, 0x0(x4)
44:  lh    x12, 0x0(x4)
48:  lb    x13, 0x0(x4)
4С:  lhu   x14, 0x0(x4)
50:  lbu   x15, 0x0(x4)
54:  auipc x16, 0x00004
58:  bne   x3,  x4, 0x08  // перескок через
5С:                       // нелегальную нулевую инструкцию
60:  jal   x17, 0x00004
64:  jalr  x14, 0x0(x17)
68:  jalr  x18, 0x4(x15)

Листинг 1. Пример программы на ассемблере.

Теперь в соответствии с кодировкой инструкций переведем программу в машинные коды:

00:  011101011100  00000 000 00001 0010011 (0x75C00093)
04:  100010100111  00000 000 00010 0010011 (0x8A700113)
08:  0000000 00010 00001 000 00011 0110011 (0x002081B3)
0C:  0000000 00010 00001 111 00100 0110011 (0x0020F233)
10:  0100000 00011 00100 000 00101 0110011 (0x403202B3)
14:  0000001 00100 00011 000 00110 0110011 (0x02418333)
18:  00000101000000000000    01111 1101111 (0x050007EF)
1C:  000000000000  00110 000 01111 1100111 (0x000307E7)
20:  0000000 11111 00101 001 00111 0010011 (0x01F29393)
24:  0100000 00001 00111 101 01000 0010011 (0x4013D413)
28:  0000000 11101 01000 101 01001 0010011 (0x01D45493)
2C:  11111010110111101100    01010 0110111 (0xFADEC537)
30:  101010111110  01010 000 01010 0010011 (0xABE50513)
34:  0000000 01010 00100 010 00000 0100011 (0x00A22023)
38:  0000000 01010 00100 001 00110 0100011 (0x00A21323)
3C:  0000000 01010 00100 000 01011 0100011 (0x00A205A3)
40:  000000000000  00100 010 01011 0000011 (0x00022583)
44:  000000000000  00100 001 01100 0000011 (0x00021603)
48:  000000000000  00100 000 01101 0000011 (0x00020683)
4C:  000000000000  00100 101 01110 0000011 (0x00025703)
50:  000000000000  00100 100 01111 0000011 (0x00024783)
54:  00000000000000000100    10000 0010111 (0x00004817)
58:  0000000 00011 00100 001 01000 1100011 (0x00321463)
5C:  00000000  00000000 00000000  00000000 (0x00000000)
60:  00000000010000000000    10001 1101111 (0x004008EF)
64:  000000000000  10001 000 01110 1100111 (0x00088767)
68:  000000000100  01111 000 10010 1100111 (0x00478967)

Листинг 2. Программа из Листинга 1, представленная в машинных кодах.

Данная программа, представленная в шестнадцатеричном формате находится в файле program.mem.

Порядок выполнения задания

  1. Внимательно ознакомьтесь микроархитектурной реализацией процессорного ядра. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
  2. Замените файл program.mem в Design Sources проекта новым файлом program.mem, приложенном в данной лабораторной работе. Данный файл содержит программу из листинга 1.
  3. Опишите модуль процессорного ядра с таким же именем и портами, как указано в задании.
    1. Процесс реализации модуля очень похож на процесс описания модуля cybercobra, однако теперь появляется:
      1. декодер
      2. дополнительные мультиплексоры и знакорасширители.
    2. Сперва рекомендуется создать все провода, которые будут подключены к входам и выходам каждого модуля на схеме.
    3. Затем необходимо создать экземпляры модулей.
    4. Также необходимо создать 32-разрядные константы I, U, S, B и J-типа и программный счётчик.
    5. После необходимо описать логику, управляющую созданными в п. 3.2 проводами.
    6. В конце останется описать логику работы программного счётчика.
  4. Опишите модуль процессорной системы, объединяющий ядро процессора (processor_core) с памятями инструкция и данных.
    1. Опишите модуль с таким же именем и портами, как указано в задании.
    2. При создании объекта модуля processor_core в модуле processor_system вы должны использовать имя сущности core (т.е. создать объект в виде: processor_core core(...).
  5. Проверьте модуль с помощью верификационного окружения, представленного в файле lab_07.tb_processor_system.sv.
    1. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня в Simulation Sources.
    2. Как и в случае с проверкой процессора архитектуры CYBERcobra, вам не будет сказано пройден тест или нет. Вам необходимо самостоятельно, такт за тактом проверить что процессор правильно выполняет описанные в Листинге 1 инструкции (см. порядок выполнения задания ЛР№4). Для этого, необходимо сперва самостоятельно рассчитать что именно должна сделать данная инструкция, а потом проверить что процессор сделал именно это.
  6. Проверьте работоспособность вашей цифровой схемы в ПЛИС.

Прочти меня, когда выполнишь. Поздравляю, ты сделал(а) свой первый взрослый процессор! Теперь ты можешь говорить:

Я способен(на) на всё! Я сам(а) полностью, с нуля, сделал(а) процессор с архитектурой RISC-V! Что? Не знаешь, что такое архитектура? Пф, щегол! Подрастешь – узнаешь

Список источников

  1. The RISC-V Instruction Set Manual Volume I: Unprivileged ISA.