vhdl
Цифровой аппаратный дизайн с использованием VHDL в двух словах
Поиск…
Вступление
В этом разделе мы предлагаем простой способ правильно спроектировать простые цифровые схемы с VHDL. Метод основан на графических блок-диаграммах и легко запоминаемом принципе:
Сначала подумайте об оборудовании, код VHDL next
Он предназначен для новичков в области цифрового аппаратного проектирования с использованием VHDL с ограниченным пониманием семантики синтеза языка.
замечания
Цифровой аппаратный дизайн с использованием VHDL прост, даже для новичков, но есть несколько важных вещей, которые нужно знать, и небольшой набор правил для подчинения. Инструмент, используемый для преобразования описания VHDL в цифровое оборудование, является логическим синтезатором. Семантика языка VHDL, используемая логическими синтезаторами, весьма отличается от семантики моделирования, описанной в Справочном руководстве по языку (LRM). Хуже того: он не стандартизирован и варьируется между инструментами синтеза.
Предлагаемый метод вводит несколько важных ограничений для простоты:
- Без защелок с уровнем срабатывания.
- Схемы синхронны на переднем фронте одного такта.
- Нет асинхронного сброса или установки.
- Нет нескольких дисков для разрешенных сигналов.
Пример блок-схемы , первый из серии из 3, кратко описывает основы цифрового оборудования и предлагает короткий список правил для разработки блок-схемы цифровой схемы. Правила помогают гарантировать простой перевод кода VHDL, который имитирует и синтезирует, как и ожидалось.
Пример кодирования объясняет перевод с блок-схемы на код VHDL и иллюстрирует ее на простой цифровой схеме.
Наконец, пример дизайна конкурса Джона Кули показывает, как применить предложенный метод на более сложном примере цифровой схемы. В нем также подробно излагаются введенные ограничения и ослабляются некоторые из них.
Блок-схема
Цифровое оборудование построено из двух типов аппаратных примитивов:
- Комбинаторные ворота (инверторы, и, или, xor, 1-битные полные сумматоры, 1-битные мультиплексоры ...) Эти логические ворота выполняют простые логические вычисления на своих входах и создают выход. Каждый раз, когда один из их входов изменяется, они начинают распространять электрические сигналы, и после короткой задержки выход стабилизируется до результирующего значения. Задержка распространения важна, поскольку она сильно связана со скоростью, с которой может работать цифровая схема, то есть с максимальной тактовой частотой.
- Элементы памяти (защелки, D-триггеры, ОЗУ ...). В отличие от комбинаторных логических элементов, элементы памяти не реагируют немедленно на изменение любого из своих входов. Они имеют входы данных, управляющие входы и выходы данных. Они реагируют на конкретную комбинацию управляющих входов, а не на любое изменение их входных данных. Например, срабатывающий D-триггер (DFF) с восходящим фронтом имеет вход синхронизации и ввод данных. На каждом нарастающем фронте тактовых импульсов вход данных дискретизируется и копируется на выход данных, который остается стабильным до следующего возрастающего фронта часов, даже если вход данных изменяется между ними.
Цифровая аппаратная схема представляет собой комбинацию комбинаторной логики и элементов памяти. Элементы памяти имеют несколько ролей. Один из них - разрешить повторное использование одной и той же комбинаторной логики для нескольких последовательных операций с разными данными. Схемы, использующие это, часто упоминаются как последовательные схемы . На следующем рисунке показан пример последовательной схемы, которая накапливает целочисленные значения с использованием того же комбинаторного сумматора, благодаря зарегистрированному регистру с восходящим фронтом. Это также наш первый пример блок-схемы.
Подкладка труб является еще одним распространенным использованием элементов памяти и основой многих микропроцессорных архитектур. Он направлен на увеличение тактовой частоты схемы путем разделения сложной обработки в последовательности более простых операций и на параллелизацию выполнения нескольких последовательных обработок:
Блок-схема представляет собой графическое представление цифровой схемы. Это помогает принимать правильные решения и хорошо понимать общую структуру перед кодированием. Это эквивалентно рекомендуемым этапам предварительного анализа во многих методах разработки программного обеспечения. Опытные дизайнеры часто пропускают эту фазу проектирования, по крайней мере, для простых схем. Однако, если вы новичок в разработке цифрового оборудования, и если вы хотите закодировать цифровую схему в VHDL, то принятие 10 простых правил, приведенных ниже, чтобы нарисовать вашу блок-диаграмму, должно помочь вам правильно понять:
- Окружайте свой рисунок большим прямоугольником. Это граница вашей цепи. Все, что пересекает эту границу, является входным или выходным портом. Объект VHDL будет описывать эту границу.
- Четко разделить регистровые регистры (например, квадратные блоки) от комбинаторной логики (например, круглые блоки). В VHDL они будут переведены в процессы, но из двух самых разных видов: синхронные и комбинаторные.
- Не используйте защелки, запускаемые с помощью уровня, используйте только начальные регистры с восходящим фронтом. Это ограничение не исходит от VHDL, который идеально подходит для моделирования защелок. Это всего лишь разумный совет для новичков. Чашечки реже нужны, и их использование создает множество проблем, которые мы, вероятно, должны избегать, по крайней мере, для наших первых проектов.
- Используйте одни и те же одиночные часы для всех ваших начальных регистров с восходящим фронтом. Опять же, это ограничение здесь ради простоты. Он не исходит от VHDL, который идеально подходит для моделирования многочасовых систем. Назовите
clock
. Он поступает извне и представляет собой вход всех квадратных блоков и только их. Если вы хотите, даже не представляете часы, это одинаково для всех квадратных блоков, и вы можете оставить его неявным в своей диаграмме. - Представляют связь между блоками с именованными и ориентированными стрелками. Для блока появляется стрелка, стрелка - это выход. Для блока стрелка идет, стрелка представляет собой вход. Все эти стрелки станут портами объекта VHDL, если они пересекают большой прямоугольник или сигналы архитектуры VHDL.
- Стрелки имеют одно единственное происхождение, но у них может быть несколько направлений. Действительно, если бы стрелка имела несколько истоков, мы бы создали сигнал VHDL с несколькими драйверами. Это не совсем невозможно, но требует особой осторожности, чтобы избежать коротких замыканий. Таким образом, мы избежим этого. Если стрелка имеет несколько мест назначения, разворачивайте стрелку столько раз, сколько необходимо. Используйте точки, чтобы различать подключенные и несвязанные переходы.
- Некоторые стрелки выходят за пределы большого прямоугольника. Это входные порты объекта. Стрелка ввода также не может быть выходом любого из ваших блоков. Это обеспечивается языком VHDL: входные порты объекта могут быть прочитаны, но не записаны. Это снова, чтобы избежать коротких замыканий.
- Некоторые стрелки выходят наружу. Это выходные порты. В версиях VHDL до 2008 года выходные порты объекта могут быть записаны, но не прочитаны. Таким образом, стрелка вывода должна иметь одно единственное происхождение и одно место назначения: снаружи. Никакие вилки на выходных стрелках, стрелка вывода не может быть также входом одного из ваших блоков. Если вы хотите использовать стрелку вывода в качестве входа для некоторых ваших блоков, вставьте новый круглый блок, чтобы разбить его на две части: внутреннюю, с как можно большим количеством вилок, и стрелку вывода, которая поступает из новой блок и выходит наружу. Новый блок станет простым непрерывным назначением в VHDL. Прозрачное переименование. Так как порты Ouptut VHDL 2008 также могут быть прочитаны.
- Все стрелки, которые не поступают или не выходят из / наружу, являются внутренними сигналами. Вы объявите их все в архитектуре VHDL.
- Каждый цикл диаграммы должен содержать по крайней мере один квадратный блок. Это не связано с VHDL. Он исходит из основных принципов проектирования цифрового оборудования. Следует избегать комбинаторных петель. За исключением очень редких случаев, они не приносят никакого полезного результата. И цикл блок-схемы, который будет включать только круглые блоки, будет комбинаторным циклом.
Не забывайте тщательно проверять последнее правило, оно так же важно, как и другие, но это может быть несколько сложнее проверить.
Если вам абсолютно не нужны функции, которые мы исключили на данный момент, например, защелки, многократные часы или сигналы с несколькими драйверами, вы должны легко составить блок-схему вашей схемы, которая соответствует 10 правилам. Если нет, проблема, вероятно, связана с схемой, которую вы хотите, а не с VHDL или логическим синтезатором. И это , вероятно , означает , что цепь вы хотите не цифровое оборудование.
Применение 10 правил к нашему примеру последовательной схемы приведет к блок-схеме, такой как:
- Большой прямоугольник вокруг диаграммы пересекается 3 стрелками, представляющими входные и выходные порты объекта VHDL.
- Блок-схема имеет два круглых (комбинаторных) блока - сумматор и блок переименования вывода - и один квадратный (синхронный) блок - регистр.
- Он использует только регистровые регистры.
- Есть только один такт, названный
clock
и мы используем только его нарастающий фронт. - Блок-схема состоит из пяти стрелок, одна с вилкой. Они соответствуют двум внутренним сигналам, двум входным портам и одному выходному порту.
- Все стрелки имеют одно происхождение и один пункт назначения, за исключением стрелки с именем
Sum
которая имеет два пункта назначения. -
Data_in
иClock
- это два наших входных порта. Они не выводятся из наших собственных блоков. -
Data_out
является наш выходной порт. Чтобы быть совместимым с версиями VHDL до 2008 года, мы добавили дополнительный переименование (круглый) блок междуSum
иData_out
. Таким образом,Data_out
имеет ровно один источник и один пункт назначения. -
Sum
иNext_sum
являются нашими двумя внутренними сигналами. - На графике ровно один цикл, и он содержит один квадратный блок.
Наша блок-схема соответствует 10 правилам. В примере кодирования будет подробно описано, как перевести блок-схемы этого типа в VHDL.
кодирование
Этот пример является вторым из серии из 3. Если вы еще этого не сделали, сначала ознакомьтесь с примером блок-схемы .
С блок-схемой, которая соответствует 10 правилам (см. Пример блок-схемы ), кодирование VHDL становится простым:
- большой окружающий прямоугольник становится объектом VHDL,
- внутренние стрелки становятся сигналами VHDL и объявляются в архитектуре,
- каждый квадратный блок становится синхронным процессом в теле архитектуры,
- каждый круглый блок становится комбинаторным процессом в структуре архитектуры.
Проиллюстрируем это на блок-схеме последовательной схемы:
Модель VHDL схемы состоит из двух блоков компиляции:
- Объект, который описывает имя схемы и ее интерфейс (имена портов, направления и типы). Это прямой перевод большого окружающего прямоугольника блок-схемы. Предполагая, что данные являются целыми числами, а
clock
используютbit
типа VHDL (только два значения:'0'
и'1'
), объектом нашей последовательной схемы может быть:
entity sequential_circuit is
port(
Data_in: in integer;
Clock: in bit;
Data_out: out integer
);
end entity sequential_circuit;
- Архитектура, которая описывает внутренности схемы (что она делает). Здесь объявляются внутренние сигналы и где все процессы создаются. Скелет архитектуры нашей последовательной схемы может быть:
architecture ten_rules of sequential_circuit is
signal Sum, Next_sum: integer;
begin
<...processes...>
end architecture ten_rules;
У нас есть три процесса для добавления к телу архитектуры, один синхронный (квадратный блок) и два комбинаторных (круглые блоки).
Синхронный процесс выглядит следующим образом:
process(clock)
begin
if rising_edge(clock) then
o1 <= i1;
...
ox <= ix;
end if;
end process;
где i1, i2,..., ix
- все стрелки, которые входят в соответствующий квадратный блок диаграммы, а o1, ..., ox
- все стрелки, которые выводят соответствующий квадратный блок диаграммы. Абсолютно ничего не изменится, кроме имен сигналов, конечно. Ничего такого. Даже не один персонаж.
Синхронный процесс нашего примера таков:
process(clock)
begin
if rising_edge(clock) then
Sum <= Next_sum;
end if;
end process;
Которые могут быть неформально переведены на: если clock
меняются, и только тогда, если изменение является нарастающим фронтом ( '0'
- '1'
), назначьте значение сигнала Next_sum
для сигнала Sum
.
Комбинаторный процесс выглядит следующим образом:
process(i1, i2,... , ix)
variable v1: <type_of_v1>;
...
variable vy: <type_of_vy>;
begin
v1 := <default_value_for_v1>;
...
vy := <default_value_for_vy>;
o1 <= <default_value_for_o1>;
...
oz <= <default_value_for_oz>;
<statements>
end process;
где i1, i2,..., in
- все стрелки, которые входят в соответствующий круглый блок диаграммы. все и не более. Мы не будем забывать стрелку, и мы не добавим ничего лишнего в список.
v1, ..., vy
- переменные, которые могут потребоваться для упрощения кода процесса. Они имеют ту же роль, что и на любом другом императивном языке программирования: сохраняйте временные значения. Перед тем, как их читать, они должны быть абсолютно назначены. Если мы не гарантируем этого, процесс не будет больше комбинаторным, поскольку он будет моделировать типы элементов памяти, чтобы сохранить значение некоторых переменных от одного процесса до следующего. Это является причиной инструкций vi := <default_value_for_vi>
в начале процесса. Обратите внимание, что значение <default_value_for_vi>
должно быть константой. Если нет, если они являются выражениями, мы могли бы случайно использовать переменные в выражениях и читать переменную перед ее назначением.
o1, ..., om
- все стрелки, которые выводят соответствующий круглый блок вашей диаграммы. все и не более. Они должны быть абсолютно назначены по крайней мере один раз во время выполнения процесса. Поскольку структуры управления VHDL ( if
, case
...) могут очень легко предотвратить назначение выходного сигнала, мы настоятельно рекомендуем назначить каждый из них, безусловно, с постоянным значением <default_value_for_oi>
в начале процесса. Таким образом, даже если оператор if
маскирует назначение сигнала, он все равно получит значение.
Абсолютно ничего не должно быть изменено на этот скелет VHDL, за исключением имен переменных, если таковые имеются, имена входов, имена выходов, значения констант <default_value_for_..>
и <statements>
. Не забывайте ни одного задания значения по умолчанию, если вы синтез будет выводить ненужные элементы памяти (скорее всего , защелки) и результат не будет то , что вы изначально хотели.
В нашем примере последовательной схемы комбинаторный процесс сумматора:
process(Sum, Data_in)
begin
Next_sum <= 0;
Next_sum <= Sum + Data_in;
end process;
Который может быть неформально переведен на: если Sum
или Data_in
(или оба) изменяют, присваивают значение 0 сигналу Next_sum
а затем снова присваивают ему значение Sum + Data_in
.
Поскольку первое присваивание (с постоянным значением по умолчанию 0
) немедленно сопровождается другим присваиванием, которое его перезаписывает, мы можем упростить:
process(Sum, Data_in)
begin
Next_sum <= Sum + Data_in;
end process;
Второй комбинаторный процесс соответствует круглому блоку, который мы добавили в стрелку вывода с более чем одним адресатом, чтобы соответствовать версиям VHDL до 2008 года. Его код просто:
process(Sum)
begin
Data_out <= 0;
Data_out <= Sum;
end process;
По той же причине, что и с другим комбинаторным процессом, мы можем упростить его как:
process(Sum)
begin
Data_out <= Sum;
end process;
Полный код для последовательной схемы:
-- File sequential_circuit.vhd
entity sequential_circuit is
port(
Data_in: in integer;
Clock: in bit;
Data_out: out integer
);
end entity sequential_circuit;
architecture ten_rules of sequential_circuit is
signal Sum, Next_sum: integer;
begin
process(clock)
begin
if rising_edge(clock) then
Sum <= Next_sum;
end if;
end process;
process(Sum, Data_in)
begin
Next_sum <= Sum + Data_in;
end process;
process(Sum)
begin
Data_out <= Sum;
end process;
end architecture ten_rules;
Примечание: мы могли бы написать три процесса в любом порядке, это ничего не изменило бы к окончательному результату в симуляции или в синтезе. Это связано с тем, что три процесса являются параллельными операциями, и VHDL рассматривает их так, как будто они действительно параллельны.
Конкурс дизайна Джона Кули
Этот пример непосредственно получен из конкурса дизайна Джона Кули на SNUG'95 (собрание групп пользователей Synopsys). Конкурс должен был противостоять разработчикам VHDL и Verilog по одной и той же проблеме дизайна. То, что Джон имел в виду, вероятно, определяло, какой язык был наиболее эффективным. Результаты состояли в том, что 8 из 9 дизайнеров Verilog смогли завершить конкурс дизайна, но ни один из 5 дизайнеров VHDL не смог. Надеюсь, используя предложенный метод, мы сделаем намного лучшую работу.
Характеристики
Наша цель - сконструировать в простой синтезируемой VHDL (сущности и архитектуре) синхронный счетчик по 512, загружаемый по модулю, по модулю 512, с выводом вывода, выдачей заимствований и выходом четности. Счетчик представляет собой 9-битный беззнаковый счетчик, поэтому он находится в диапазоне от 0 до 511. Спецификация интерфейса счетчика приведена в следующей таблице:
название | Бит ширины | направление | Описание |
---|---|---|---|
ЧАСЫ | 1 | вход | Мастер-часы; счетчик синхронизируется по нарастающему фронту CLOCK |
DI | 9 | вход | Шина ввода данных; счетчик загружается с DI, когда UP и DOWN являются низкими |
UP | 1 | вход | Команда умножения на 3; когда UP высокий, а DOWN - низкий, счетчик увеличивается на 3, обертывая его максимальное значение (511) |
ВНИЗ | 1 | вход | Команда подсчета числа «вниз-5»; когда DOWN высокий и UP низкий, счетчик уменьшается на 5, обертывая его минимальное значение (0) |
Колорадо | 1 | Выход | Выполнить сигнал; высокий только при подсчете выше максимального значения (511) и, таким образом, обертывания |
BO | 1 | Выход | Заимствовать сигнал; высокий только при подсчете ниже минимального значения (0) и, таким образом, обертывания |
ДЕЛАТЬ | 9 | Выход | Выходная шина; текущее значение счетчика; когда UP и DOWN являются высокими, счетчик сохраняет свое значение |
ПО | 1 | Выход | Сигнал четности; если текущее значение счетчика содержит четное число 1 |
При подсчете превышения максимального значения или при подсчете ниже минимального значения счетчик обтекает:
Текущее значение счетчика | ВВЕРХ ВНИЗ | Счетчик следующего значения | Следующий СО | Следующий БО | Следующий ПО |
---|---|---|---|---|---|
Икс | 00 | DI | 0 | 0 | четности (DI) |
Икс | 11 | Икс | 0 | 0 | соотношение (х) |
0 ≤ x ≤ 508 | 10 | х + 3 | 0 | 0 | четности (х + 3) |
509 | 10 | 0 | 1 | 0 | 1 |
510 | 10 | 1 | 1 | 0 | 0 |
511 | 10 | 2 | 1 | 0 | 0 |
5 ≤ x ≤ 511 | 01 | х-5 | 0 | 0 | четности (х-5) |
4 | 01 | 511 | 0 | 1 | 0 |
3 | 01 | 510 | 0 | 1 | 1 |
2 | 01 | 509 | 0 | 1 | 1 |
1 | 01 | 508 | 0 | 1 | 0 |
0 | 01 | 507 | 0 | 1 | 1 |
Блок-схема
Основываясь на этих спецификациях, мы можем приступить к разработке блок-схемы. Сначала представим интерфейс:
Наша схема имеет 4 входа (включая часы) и 4 выхода. Следующий шаг состоит в том, чтобы решить, сколько регистров и комбинаторных блоков мы будем использовать и какими будут их роли. Для этого простого примера мы выделим один комбинаторный блок для вычисления следующего значения счетчика, выполнения и заимствования. Другой комбинаторный блок будет использоваться для вычисления следующего значения контроля четности. Текущие значения счетчика, выполнение и заимствование будут храниться в регистре, тогда как текущее значение контроля четности будет храниться в отдельном регистре. Результат показан на рисунке ниже:
Проверка того, что блок-схема соответствует нашим 10 правилам проектирования, быстро выполняется:
- Наш внешний интерфейс правильно представлен большим окружающим прямоугольником.
- Наши 2 комбинаторных блока (круглые) и наши 2 регистра (квадрат) четко разделены.
- Мы используем только инициируемые фронтом регистры.
- Мы используем только один такт.
- У нас есть 4 внутренних стрелки (сигналы), 4 входных стрелки (входные порты) и 4 выходных стрелки (выходные порты).
- Ни одна из наших стрел имеет несколько истоков. У трех есть несколько пунктов назначения (
clock
,ncnt
иdo
). - Ни одна из наших 4 входных стрелок не выводит наши внутренние блоки.
- Три из наших выходных стрелок имеют ровно одно происхождение и один пункт назначения. Но у
do
есть 2 направления: снаружи и один из наших комбинаторных блоков. Это нарушает правило номер 8 и должно быть исправлено путем вставки нового комбинаторного блока, если мы хотим соответствовать версиям VHDL до 2008 года:
- Теперь у нас есть ровно 5 внутренних сигналов (
cnt
,nco
,nbo
,ncnt
иnpo
). - На диаграмме есть только один цикл, образованный
cnt
иncnt
. В цикле есть квадратный блок.
Кодирование в версиях VHDL до 2008 г.
Перевод нашей блок-схемы в VHDL прост. Текущее значение счетчика колеблется от 0 до 511, поэтому мы будем использовать 9- bit_vector
бит- bit_vector
сигнал для его представления. Единственная тонкость возникает из-за необходимости выполнять побитовое (например, вычисление четности) и арифметические операции с одними и теми же данными. Стандартный пакет numeric_bit
библиотеки ieee
решает это: он объявляет unsigned
тип с точно таким же объявлением, что и bit_vector
и перегружает арифметические операторы таким образом, что они принимают любую смесь unsigned
и целых чисел. Чтобы вычислить выполнение и заимствование, мы будем использовать 10-битное unsigned
временное значение.
Объявления библиотеки и организация:
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
Скелет архитектуры:
architecture arc1 of cooley is
signal cnt: unsigned(8 downto 0);
signal ncnt: unsigned(8 downto 0);
signal nco: bit;
signal nbo: bit;
signal npo: bit;
begin
<...processes...>
end architecture arc1;
Каждый из 5 блоков моделируется как процесс. Синхронные процессы, соответствующие нашим двум регистрам, очень легко кодировать. Мы просто используем шаблон, предложенный в примере кодирования . Регистр, который хранит флаг четности, например, закодирован:
poreg: process(clock)
begin
if rising_edge(clock) then
po <= npo;
end if;
end process poreg;
и другой регистр, в котором хранятся co
, bo
и cnt
:
cobocntreg: process(clock)
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
cnt <= ncnt;
end if;
end process cobocntreg;
Переименование комбинаторного процесса также очень просто:
rename: process(cnt)
begin
do <= (others => '0');
do <= bit_vector(cnt);
end process rename;
Вычисление четности может использовать переменную и простой цикл:
parity: process(ncnt)
variable tmp: bit;
begin
tmp := '0';
npo <= '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
npo <= not tmp;
end process parity;
Последний комбинаторный процесс является самым сложным из всех, но строго применяя предлагаемый метод перевода, также упрощает:
u3d5: process(up, down, di, cnt)
variable tmp: unsigned(9 downto 0);
begin
tmp := (others => '0');
nco <= '0';
nbo <= '0';
ncnt <= (others => '0');
if up = '0' and down = '0' then
ncnt <= unsigned(di);
elsif up = '1' and down = '1' then
ncnt <= cnt;
elsif up = '1' and down = '0' then
tmp := ('0' & cnt) + 3;
ncnt <= tmp(8 downto 0);
nco <= tmp(9);
elsif up = '0' and down = '1' then
tmp := ('0' & cnt) - 5;
ncnt <= tmp(8 downto 0);
nbo <= tmp(9);
end if;
end process u3d5;
Обратите внимание, что два синхронных процесса также могут быть объединены и что один из наших комбинаторных процессов может быть упрощен в простой параллельной передаче сигнала. Полный код с декларациями библиотеки и пакетов и с предлагаемыми упрощениями выглядит следующим образом:
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
architecture arc2 of cooley is
signal cnt: unsigned(8 downto 0);
signal ncnt: unsigned(8 downto 0);
signal nco: bit;
signal nbo: bit;
signal npo: bit;
begin
reg: process(clock)
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
po <= npo;
cnt <= ncnt;
end if;
end process reg;
do <= bit_vector(cnt);
parity: process(ncnt)
variable tmp: bit;
begin
tmp := '0';
npo <= '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
npo <= not tmp;
end process parity;
u3d5: process(up, down, di, cnt)
variable tmp: unsigned(9 downto 0);
begin
tmp := (others => '0');
nco <= '0';
nbo <= '0';
ncnt <= (others => '0');
if up = '0' and down = '0' then
ncnt <= unsigned(di);
elsif up = '1' and down = '1' then
ncnt <= cnt;
elsif up = '1' and down = '0' then
tmp := ('0' & cnt) + 3;
ncnt <= tmp(8 downto 0);
nco <= tmp(9);
elsif up = '0' and down = '1' then
tmp := ('0' & cnt) - 5;
ncnt <= tmp(8 downto 0);
nbo <= tmp(9);
end if;
end process u3d5;
end architecture arc2;
Дальше
Предлагаемый метод прост и безопасен, но он опирается на несколько ограничений, которые могут быть ослаблены.
Пропустить рисунок блок-схемы
Опытные дизайнеры могут пропустить чертеж блок-схемы для простых конструкций. Но они все еще думают о аппаратном обеспечении. Они рисуют в голове, а не на листе бумаги, но они как-то продолжают рисовать.
Использовать асинхронные сбросы
Существуют ситуации, когда асинхронные сбросы (или комплекты) могут улучшить качество дизайна. Предлагаемый метод поддерживает только синхронные сбрасывания (это сбросы, которые принимаются во внимание по нарастающим фронтам часов):
process(clock)
begin
if rising_edge(clock) then
if reset = '1' then
o <= reset_value_for_o;
else
o <= i;
end if;
end if;
end process;
Версия с асинхронным сбросом изменяет наш шаблон, добавляя сигнал сброса в список чувствительности и давая ему наивысший приоритет:
process(clock, reset)
begin
if reset = '1' then
o <= reset_value_for_o;
elsif rising_edge(clock) then
o <= i;
end if;
end process;
Объединить несколько простых процессов
Мы уже использовали это в окончательной версии нашего примера. Слияние нескольких синхронных процессов, если все они имеют одинаковые часы, тривиально. Слияние нескольких комбинаторных процессов в одном также тривиально и является просто простой реорганизацией блок-схемы.
Мы также можем объединить некоторые комбинаторные процессы с синхронными процессами. Но для этого мы должны вернуться к нашей блок-схеме и добавить одиннадцатое правило:
- Группируйте несколько круглых блоков и по крайней мере один квадратный блок, рисуя вокруг них ограждение. Также приложите стрелки, которые могут быть. Не позволяйте стрелке пересекать границу корпуса, если она не приходит или не выходит из / за пределы корпуса. Как только это будет сделано, посмотрите на все выходные стрелки корпуса. Если какой-либо из них поступает из круглого блока корпуса или также является входом в корпус, мы не можем объединить эти процессы в синхронном процессе. Иначе мы можем.
Например, в нашем примере счетчика мы не смогли сгруппировать два процесса в красном корпусе следующего рисунка:
потому что ncnt
является выходом оболочки, а его начало - круглым (комбинаторным) блоком. Но мы могли бы группировать:
Внутренний сигнал npo
станет бесполезным, и в результате процесс будет следующим:
poreg: process(clock)
variable tmp: bit;
begin
if rising_edge(clock) then
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
end if;
end process poreg;
которые также могут быть объединены с другим синхронным процессом:
reg: process(clock)
variable tmp: bit;
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
cnt <= ncnt;
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
end if;
end process reg;
Группировка может быть даже такой:
Приводя к гораздо более простой архитектуре:
architecture arc5 of cooley is
signal cnt: unsigned(8 downto 0);
begin
process(clock)
variable ncnt: unsigned(9 downto 0);
variable tmp: bit;
begin
if rising_edge(clock) then
ncnt := '0' & cnt;
co <= '0';
bo <= '0';
if up = '0' and down = '0' then
ncnt := unsigned('0' & di);
elsif up = '1' and down = '0' then
ncnt := ncnt + 3;
co <= ncnt(9);
elsif up = '0' and down = '1' then
ncnt := ncnt - 5;
bo <= ncnt(9);
end if;
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
cnt <= ncnt(8 downto 0);
end if;
end process;
do <= bit_vector(cnt);
end architecture arc5;
с двумя процессами (параллельное назначение сигнала do
является сокращением эквивалентного процесса). Решение только с одним процессом остается в качестве упражнения. Остерегайтесь, это вызывает интересные и тонкие вопросы.
Еще дальше
Заблокированные по уровню защелки, падающие кромки часов, несколько часов (и ресинхронизаторы между часовыми доменами), несколько драйверов для одного и того же сигнала и т. Д. Не являются злыми. Иногда они полезны. Но изучение того, как их использовать и как избежать связанных ошибок, выходит далеко за рамки этого краткого введения в цифровой аппаратный дизайн с VHDL.
Кодирование в VHDL 2008
VHDL 2008 представил несколько модификаций, которые мы можем использовать для дальнейшего упрощения кода. В этом примере мы можем воспользоваться двумя модификациями:
- выходные порты могут быть прочитаны, нам больше не нужен сигнал
cnt
, - унарный оператор
xor
может использоваться для вычисления четности.
Код VHDL 2008 может быть:
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
architecture arc6 of cooley is
begin
process(clock)
variable ncnt: unsigned(9 downto 0);
begin
if rising_edge(clock) then
ncnt := unsigned('0' & do);
co <= '0';
bo <= '0';
if up = '0' and down = '0' then
ncnt := unsigned('0' & di);
elsif up = '1' and down = '0' then
ncnt := ncnt + 3;
co <= ncnt(9);
elsif up = '0' and down = '1' then
ncnt := ncnt - 5;
bo <= ncnt(9);
end if;
po <= not (xor ncnt(8 downto 0));
do <= bit_vector(ncnt(8 downto 0));
end if;
end process;
end architecture arc6;