Как обнулить память verilog
Сегодня рассмотрим один из важнейших вопросов разработки аппаратного обеспечения. Разговор будет о памяти с произвольным доступом . В такой памяти можно обратиться к произвольной ячейке при помощи специальной шины, задающей адрес ячейки памяти.
Устройство памяти
Основу памяти составляют двоичные слова и схема их выбора. Двоичное слово еще называют ячейкой памяти. По состоянию шины адреса происходит активация той или иной ячейки памяти и в зависимости от состояния линии разрешения можно считать или записать данные в ячейку.
Самый простой модуль памяти имеет шину данных. Для выборки нужной ячейки необходима шина адреса ( A ). При помощи линии разрешения записи ( WE ) можно произвести сохранение информации в память. Для общей синхронизации всех процессов в проекте необходим вход тактовых сигналов ( CLK ).
Поведенческий стиль описания позволит сэкономить массу времени и сделать меньше ошибок. Давайте посмотрим насколько это будет просто. Для удобства сделаем модуль памяти параметризированным. В качестве параметров выделим ширину адресной шины ( ADDR_WIDTH ). Пусть в ней будут 4 линии. Также будет удобным определить ширину шины данных ( DATA_WIDTH ) . И так называемую глубину ( DEPTH ). Глубина это количество ячеек памяти. Разумеется, между количеством линий адресной шины и глубиной есть связь, но давайте для простоты и наглядности не будем городить функции. Количество ячеек 16 .
С параметрами закончили. Описываем входы и выходы. Обязателен вход для тактовых сигналов. Шина адреса. Шина данных на входе и выходе. Линия разрешения записи.
Самый важный момент во всем модуле это описание хранилища данных.
reg [DATA_WIDTH-1:0] mem [DEPTH-1:0];
В процедурном блоке по переднему фронту тактового сигнала если разрешающая линия уровня 1 , происходит запись состояния на входной шине данных в ячейку памяти, адрес которой выставлен на шине адреса.
Выход модуля памяти data_o соединен с ячейкой, адрес которой выставлен на шине адреса mem[addr] . Посмотрим как работает этот модуль. Сразу создадим тестовый стенд.
Посмотрим временные диаграммы. Все работает как нужно.
Довольно часто в проектах приходится хранить двоичные слова как некоторые константы. Для таких случаев применяют упрощенную конструкцию памяти. Поскольку запись в такую память не предусмотрена, то удаляется все что с этим связано. Это линия разрешения записи и даже сам тактовый сигнал не нужен. Остается адрес на входе и двоичное слово на выходе. При начальной установке описывается содержимое ячеек памяти.
При помощи этого содержимого можно преобразовать двоичное число в код, предназначенный для семисегментного индикатора.
Он состоит из полупроводниковых материалов, способных светиться при подаче напряжения между катодом и анодом. Строго говоря, принцип свечения может быть различным. Отдельными сегментами можно отображать арабские цифры, что гораздо удобнее для восприятия пользователем. Сегменты нумеруются сверху по часовой стрелке от A до центральной перекладины G .
Создаем проект с использованием памяти
Создадим модуль, использующий память. В нем позволим пользователю вводить двоичный код числа, а на индикаторе будет отображаться его привычный вид. Четыре бита под код bin , одна линия для активации индикатора на отладочном комплекте seg_select , одна для деактивации матрицы индикаторов matrix_select , три бита под код позиции position , где будет отображена цифра и семь линий для сегментов цифры. Сейчас не обращайте внимание на количество линий, это особенности изделия .
Вписываем в главный модуль созданный блок памяти и соединяем его с нужными шинами. Задаем позицию для числа, активируем и деактивируем компоненты на отладочном комплекте.
Осталось разобраться с одним небольшим, но очень важным моментом. Какие ресурсы ПЛИС использует модуль памяти. Смотрим отчет о синтезе и отмечаем, что задействовано 7 логических ячеек. При этом блочной памяти не задействовано нисколько. Это сейчас кажется, что не страшно и никакой проблемы в этом нет. Но это только пока проекты небольшие. А потом сложность наших проектов возрастет и будем работать на пределе ресурсов ПЛИС. Вот тогда какие ресурсы использованы будет очень важным. Стоит увеличить количество слов в памяти до нескольких килобайт, ресурсы ПЛИС растают прямо на глазах.
Использование ресурсов блочной памяти
Сейчас в описании памяти появилась новая линия cs . Она служит для ее активации. Если память не активирована, то на ее выходах появляются состояния высокого сопротивления, что будет означать физическое рассоединение с остальным проектом. Еще одна особенность. Это регистр для временного хранения содержимого ячейки памяти tmp_data .
Запись в память проходит как обычно, а вот чтение происходит во временный регистр tmp_data . Выход модуля связан не с ячейкой памяти, а с этим временным регистром, причем особым образом.
Это условное присвоение. Слева от вопросительного знака условие, которое может быть истинным или ложным, справа варианты при истинном и ложном значении условия. До двоеточия при истине, после при ложном значении. Все это означает, что на выход модуля пойдет содержимое вр+еменного регистра или на выходах появится высокоомное Z — состояние. Содержимое временного регистра отправится на выход только когда одновременно будет единица на линии активности модуля и ноль на линии записи.
Отчет о синтезе показывает, что теперь блочная память используется.
Так решил компилятор. Что же такое произошло, что данные поместились в совсем другой ресурс? Дополнительная линия активации модуля тут совершенно ни при чем. Это просто удобная штука, позволяющая многим устройствам быть подключенным к единой шине. Просто в один момент времени подключено что-то одно, а остальные устройства при этом в состоянии высокого сопротивления. Быть может это дополнительный временный регистр или разрядность шины данных? Строго говоря, при поведенческом стиле описания модулей единственной гарантией использования ресурсов блочной памяти является отчет о синтезе. Прямое управление ресурсами будет рассмотрено позже
Видео-обзор с канала YouTube
Поддержите статью лайком если понравилось и подпишитесь чтобы ничего не пропускать.
В данной статье разбор простейшей реализации RAM на языке Verilog.
Перед тем, как перейти к разбору кода, рекомендуется изучить базовый синтаксис языка Verilog.
Шаг 1: объявление модуля с соответствующими входными/выходными сигналами
- data — данные для записи.
- addr — адрес к участку памяти в RAM.
- wr — статус (считывание/запись).
- clk — clock cycle системы.
- response — готовность RAM (1 — если RAM обработал запрос на считывание/запись, 0 — в противном случае).
- out — данные, считанные из RAM.
Шаг 2: объявление регистров внутри модуля
Объявление массива для хранения данных:
Также нам понадобится хранить предыдущие входные параметры с целью отслеживания их изменений в always блоке:
И последние два регистра для обновления выходных сигналов после вычислений в always блоке:
Шаг 3: реализация логики always блока
Always блок срабатывает на negedje, т.е. в момент перехода clock-а с 1 на 0. Это сделано для правильной синхронизации RAM-а c кэшем. Иначе возможны случаи, когда RAM не успевает сбросить статус готовности с 1 на 0 и на следующем clock-е кэш решает, что RAM благополучно обработал его запрос, что в корне неверно.
Логика алгоритма always блока такова: если данные обновлены, сбрасываем статус готовности на 0 и записываем/считываем данные, если запись/считывание выполнены, обновляем статус готовности на 1.
В конце добавляем следующий участок кода:
Тип выходных сигналов нашего модуля — wire. Единственный способ изменения сигналов данного типа — долгосрочное присваивание, являющееся запрещённым внутри always блока. По этой причине в always блоке используются регистры, которые в дальнейшем присваиваются выходным сигналам.
Direct mapping cache
Direct mapping cache — один из наиболее простых видов кэша. В данной реализации кэш состоит из n элементов, а RAM условно делится на блоки по n, тогда i-ому элементу в кэше соответствуют все такие k-ые элементы в RAM, удовлетворяющие условию i = k % n.
На приведённом ниже рисунке изображён кэш размером 4 и RAM размером 16.
Каждый элемент кэша содержит следующую информацию:
- бит валидности — является ли информация в кэше актуальной.
- тэг — номер блока в RAM, где находится этот элемент.
- данные — информация, которую мы записываем/считываем.
Шаг 1: объявление модуля с соответствующими входными/выходными сигналами
Объявление модуля кэша идентично RAM-у, за исключением нового выходного сигнала is_missrate. Этот выходной сигнал хранит информацию о том, был ли последний запрос на считывание missrate-ом.
Шаг 2: объявление регистров и RAM-а
Перед тем, как объявить регистры, определим размеры кэша и индекса:
Далее, объявляем массив, в котором и будут храниться данные, которые мы записываем и считываем:
Также нам нужно хранить биты валидности и тэги для каждого элемента в кэше:
Регистры, в которые будет разбит входной адрес:
Регистры, хранящие в себе входные значения на предыдущим clock-е(для отслеживания изменений входных данных):
Регистры для обновления выходных сигналов после вычислений в always блоке:
Выходные значения для RAM:
Объявление модуля RAM и подключение входных и выходных сигналов:
Шаг 3: реализация логики always блока
Начнём с того, что на каждый clock у нас есть два состояния — входные данные изменены, либо не изменены. Исходя из этого мы имеем следующее условие:
Блок 1. В случае, если входные данные изменены, первым делом мы сбрасываем статус готовности на 0:
Далее мы обновляем регистры, хранившие входные значения предыдущего clock-а:
Разбиваем входной адрес на тэг и индекс:
Для вычисления тэга используется побитовый сдвиг вправо, для индекса достаточно просто присвоить, т.к. лишние разряды адреса не учитываются.
Следующий шаг — выбор между записью и считыванием:
В случае записи первоначально мы изменяем данные в кэше, затем обновляем входные данные для RAM-а. В случае считывания мы проверяем наличие данного элемента в кэше и, если он есть, записываем его в out_reg, в противном случае обращаемся в RAM.
Блок 2. Если данные не были изменены с момента выполнения предыдущего clock-а, то мы имеем следующий код:
Здесь мы ждём окончания выполнения обращения в RAM(в случае если обращения не было, ram_response равен 1), обновляем данные, если была команда на считывание и устанавливаем готовность кэша на 1.
Комментарии делятся на:
- Однострочные (//);
- Блочные (/* текст комментария или часть программы */).
Однострочными комментариями можно закомментировать часть одной строки или всю строку полностью.
Из примера видно, что строка №34 закомментирована полностью. В то время как в строке №36 закомментировали часть строки «Rc3 & Rc4» и заменили на выражение «Rc3 | Rc4». А к строке №35 написали комментарий.
Блочные комментарии дают возможность закомментировать участок программы. Но следует отметить, что рассматриваемый тип комментариев не позволяет комментировать часть кода в строке.
Хотелось бы отметить, что именно комментарии дают возможность написать заголовок к листингу. То есть отразить непосредственно в коде программы автора программы, назначение блока и т.п.
В Verilog имя (идентификатор) - последовательность букв и цифр, знаков «$» и «_», причем начинаться оно обязано не с цифры. Регистр имеет значение. Если начальный символ – «\», то следом за ним может идти любая последовательность символов. Все, что до пробела, будет считаться корректным именем. Например: «Character», «cHaracter», «$Character», «\c+Ha^racter».
Правило объявления постоянных (констант) в Verilog имеет следующий вид
[размер]['система счисления] значение константы
При объявлении размера указывается число бит в слове константы, а в системе счисления указывается соответствующий код (d = десятичный, b = двоичный, o = восьмеричный, h = шестнадцатеричный). По умолчанию система счисления воспринимается как десятичная.
Пунктуация - пробелы игнорируются в Verilog. Точка с запятой используется для обозначения конца командной строки. Запятые, как правило, используются для отдельных элементов в списке.
Значения сигналов - сигналы в Verilog могут принимать одно из четырех значений:
- 0 (логический 0);
- 1 (логическая 1);
- X (не задан или не определен);
- Z (состояние высокого импеданса; для применения тристабильных состояний).
Параметры - параметр в Verilog может быть любая постоянная величина. Параметры используются для унификации блоков (макросов). Например, 4-разрядный сумматор становится полезнее если его описать как n-разрядный сумматор, где n – параметр разрядности, задаваемый пользователем перед компиляцией. Ниже приведены некоторые типичные примеры применения параметров:
Память - Verilog позволяет использовать двумерные массивы, которые обычно используют как память (ОЗУ).
Из примера видно, что объявляем регистр «m», как двумерный массив, состоящий из 64-х восьми-битных слов. Вы можете получить доступ к любому из 64-х слов указав, например m [2].
Verilog 2001 поддерживает 2-уровневый операции по чтению объявленного массива, такие как m [2], [3]. Таким образом получаете доступ к отдельным битам считываемого байта.
Verilog - язык описания цифровых схем. На первом уроке познакомимся с базовыми типами источников сигнала используемыми в языке.
Пожалуй было бы не плохо начать наше обсуждение с понятия сигнал ( signal ). Сигналы – это электрические импульсы, которые передаются по проводам ( wire ) между логическими элементами схемы. Провода переносят информацию не производя над ней никаких вычислений. В цифровой схеме сигналы важны для передачи двоичных данных.
Базовый тип источника сигнала в языке Verilog – это провод, wire . Таким образом, если у вас есть арифметическое или логическое выражение, вы можете ассоциировать результат выражения с именованным проводом и позже использовать его в других выражениях. Это немного похоже на переменные, только их (как провода в схеме) нельзя пересоединить на лету, нельзя поменять назначение. Значение провода ( wire ) – это функция того, что присоединено к нему.
Вот пример декларации однобитного провода в “программе” Verilog:
Регистры описываются так же как и провода:
reg [3:0] m;
reg [0:100] n;
Они могут использоваться так же, как и провода в правой части выражений, как операнды:
wire [1:0] p = m[2:1];
Вы можете определить массив регистров, которые обычно называют “память” ( RAM ):
reg [7:0] q [0:15]; //память из 16 слов, каждое по 8 бит
Еще один тип источника сигнала – это integer . Он похож на регистр reg , но всегда является 32х битным знаковым типом данных. Например, объявим:
integer loop_count;
Verilog позволяет группировать логику в блоки. Каждый блок логики называется “модулем” ( module ). Модули имеют входы и выходы, которые ведут себя как сигналы wire .
При описании модуля сперва перечисляют его порты (входы и выходы):
module my_module_name (port_a, port_b, w, y, z);
А затем описывают направление сигналов:
input port_a;
output [6:0] port_b;
input [0:4] w;
inout y; //двунаправленный сигнал, обычно используется
//только для внешних контактов микросхем
Числа – это числа. Они могут использоваться во всяких арифметических и логических выражениях. Например, можно прибавить 1 к вектору “ aa ”:
wire [3:0] aa;
wire [3:0] bb;
assign bb = aa + 1;
На следующих уроках будет рассказано про некоторые арифметические и логические функции.
Читайте также: