Files
APS/Labs/06. Main memory/README.md
Andrei Solodovnikov abdc91a70f ЛР6. Восстановление логики ready.
Перечитав спеку АПБ, я понял что ведущие устройства все-таки могут
хардкодить ready в 1.
2024-07-02 10:53:07 +03:00

15 KiB
Raw Blame History

Лабораторная работа 6 "Основная память"

Процессор CYBERcobra 2000 использовал в качестве основного хранилища данных регистровый файл, однако на практике 31-го регистра недостаточно для выполнения сложных программ. Для этих целей используется основная память, роль которой в нашей системе будет выполнять память данных.

Цель

Описать память данных, с побайтовой адресацией.

Материал для подготовки к лабораторной работе

Для успешного выполнения лабораторной работы, вам необходимо использовать навыки, полученные при написании лабораторной работы №3 "Регистровый файл и память инструкций";

Теория

В задании по реализации памяти инструкций лабораторной работы №3 байтовая адресация была описана следующим образом:

Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплен свой индивидуальный адрес).

Данное описание было дано не совсем корректным образом, чтобы в третьей лабораторной работе было более четкое понимание задания. В чем заключается некорректность? Процессор должен быть способен не только обращаться к отдельным байтам в памяти, но и обновлять в памяти любой отдельный байт, а также считывать отдельные байты.

Вопрос считывания отдельного байта будет решаться специальным модулем загрузки и сохранения, памяти данных при этом будет достаточно возвращать всё слово, содержащее запрашиваемый байт как это было уже делалось для памяти инструкций.

Нас интересует возможность памяти обновлять любой из байт в слове. Подобный функционал часто используется при реализации памяти и в системных интерфейсах, например AXI4 или APB. Для этого используется специальный сигнал, который называется byte enable. Разрядность этого сигнала равна числу байт в шине данных (в нашем случае разрядность byte enable составляет 4). Вы можете представить этот сигнал, как 4 провода, каждый из которых является сигналом разрешения записи для отдельной памяти с шириной данный в 1 байт.

Давайте разберемся как это будет работать. Допустим, мы хотим записать значение 0xA5 по адресу 0x6. Поскольку мы работаем с байтовой адресацией, как и при реализации памяти инструкций, пришедший адрес необходимо будет разделить на 4 (см. рис. 1). В итоге мы получим указатель на первую 32-битную ячейку памяти (6 / 4 = 1). Однако, чтобы пришедшие данные были в итоге записаны не в нулевой байт первого слова (четвертый байт памяти), во второй, мы будем использовать сигнал byte enable, второй бит которого будет равен единице.

Чтобы данные остальных байт не были испорчены, при описании памяти на SystemVerilog нужно разделить запись в отдельные байты. Для того, чтобы получить доступ к отдельным диапазонам бит ячейки памяти, после указания индекса ячейки необходимо указать диапазон бит, к которым вы хотите получить доступ. К примеру, чтобы получить доступ к битам с 5-го по 3-ий 18-ой ячейки памяти, необходимо использовать следующую запись:

mem[18][5:3];

Учитывайте и то, что комбинации значений бит в сигнале byte enable могут быть любыми: 0000, 0100, 0110, 1111 и т.п.

Задание

Реализовать память данных с поддержкой обновления отдельных байт в выбранной ячейке памяти.

У данного модуля будет шесть входных/выходных сигналов:

  • вход тактового синхроимпульса
  • вход запроса на работу с памятью
  • вход сигнала разрешения записи
  • 32-битный вход адреса
  • 32-битный вход данных записи
  • 32-битный выход данных синхронного чтения

Прототип модуля следующий:

module data_mem
import memory_pkg::DATA_MEM_SIZE_BYTES;
import memory_pkg::DATA_MEM_SIZE_WORDS;
(
  input  logic        clk_i,
  input  logic        mem_req_i,
  input  logic        write_enable_i,
  input  logic [ 3:0] byte_enable_i,
  input  logic [31:0] addr_i,
  input  logic [31:0] write_data_i,
  output logic [31:0] read_data_o,
  output logic        ready_o
);

Как и память инструкций, память данных будет состоять из 32-битных ячеек, количество которых определяется параметром. Как и в памяти инструкций, необходимо использовать только младшие биты адреса в количестве, равном логарифму по основанию 2 от количества ячеек памяти, начиная со второго бита (см. код памяти инструкций из ЛР№3).

Отличие от памяти инструкций будет заключаться в:

  • синхронном порте на чтение;
  • наличии порта на запись;
    • посредством этого порта на запись можно менять отдельные байты ячейки памяти.

Синхронный порт на чтение означает, что выдача данных по предоставленному адресу осуществляется не сразу же, а на следующий такт (см. рис. 2). Для этого, перед выходным сигналом ставится отдельный регистр. Таким образом, выдача данных с порта на чтение будет осуществляться не с помощью непрерывного присваивания, а посредством блока always_ff (см. практическую часть ЛР№3).

Также в памяти появилось три управляющих сигнала:

  • mem_req_i,
  • write_enable_i,
  • byte_enable_i

и один статусный:

  • ready_o.

Сигнал mem_req_i является сигналом запроса на работу с памятью. Без этого сигнала память не должна выполнять операции чтения/записи. Как сделать так, чтобы не происходило чтение без запроса? Например, не обновлять значение, считанное во время предыдущей операции чтения.

Сигнал write_enable_i является сигналом разрешения записи. Этот сигнал определяет, является ли пришедший запрос в память запросом на запись, либо же запросом на чтение.

Если mem_req_i == 1 и write_enable_i == 0, то происходит запрос на чтение из памяти. В этом случае, необходимо записать в выходной регистр read_data_o значение из ячейки, на которую указывает addr_i. Во всех других случаях чтение из памяти не производится (read_data_o сохраняет предыдущее значение).

../../.pic/Labs/lab_06_main_memory/fig_02.wavedrom.svg

Рисунок 2. Операции запросов на чтение.

Если mem_req_i == 1 и write_enable_i == 1, то происходит запрос на запись в память. В этом случае, необходимо записать значение write_data_i в ячейку по, на которую указывает addr_i. Во всех других случаях (любой из сигналов mem_req_i, write_enable_i равен нулю), запись в память не производится. Запись необходимо производить только в те байты указанной ячейки, которым соответствуют биты сигнала byte_enable_i, равные единице.

../../.pic/Labs/lab_06_main_memory/fig_03.wavedrom.svg

Рисунок 3. Операции запросов на запись.

Выход ready_o в данном модуле должен всегда быть равен единице, поскольку данные всегда будут выдаваться на следующий такт. В реальности, обращение в память может занимать сотни тактов процессора, причем их число бывает недетерминированным (нельзя заранее предсказать сколько тактов займет очередной запрос в память). Именно поэтому стандартные интерфейсы обычно используют такие сигналы как ready или valid, позволяющие синхронизировать разные блоки системы. Сигнал ready_o в нашем интерфейсе используется сигнала о задержке в выдаче данных. В случае, если устройству нужно больше одного такта, чтобы выдать данные, он устанавливает на данный сигнал значение 0 до тех пор, пока данные не будут готовы.

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

  1. Внимательно ознакомьтесь с заданием. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
  2. Реализуйте память данных. Для этого:
    1. В Design Sources проекта создайте SystemVerilog-файл data_mem.sv.
    2. Опишите в нем модуль памяти данных с таким же именем и портами, как указано в задании.
      1. Описание модуля будет схожим с описанием модуля памяти инструкций, однако порт чтения в этот раз будет синхронным (запись в него будет происходить в блоке always_ff). Количество ячеек памяти данных определяется параметром DATA_MEM_SIZE_WORDS, определенным в memory_pkg. Кроме того, необходимо будет описать логику записи данных в память.
      2. Запись в ячейки памяти описывается подобно записи данных в регистры, только при этом, происходит доступ к конкретной ячейке памяти с помощью входа addr_i.
      3. Перед тем как обратиться к ячейке памяти, значение с addr_i необходимо преобразовать по аналогии с памятью инструкций.
      4. Обратите внимание что работа с памятью должна осуществляться только когда сигнал mem_req_i == 1. В противном случае запись не должна производиться, а на шине read_data_o должен оставаться результат предыдущего чтения.
      5. При этом запись должна вестись только в те байты выбранной ячейки памяти, которым соответствуют биты сигнала byte_enable_i, выставленные в 1.
      6. У памяти есть дополнительный выход ready_o, который всегда равен единице.
    3. После описания памяти данных, её необходимо проверить с помощью тестового окружения.
      1. Тестовое окружение находится здесь.
      2. Для запуска симуляции воспользуйтесь этой инструкцией.
      3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (tb_data_mem).
      4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!