Может, мне и удалось бы оттянуть этот момент, но сосед попросил сделать генератор прямоугольных импульсов, чтобы наглядно отображались и можно было бы управлять частотой и длительностью импульса.
И с точностью в 0.1 микросекунды…
И мой взгляд упал на платку с CPLD (рублей за 200, вроде) на которой были и индикаторы и кнопки. Когда-то стоит начинать работать с такой штукой, подумал я и…
Выбор на чем писать VHDL или Verilog не стоял — хотя и пишу все на С, но люблю все же Ada — так что VHDL однозначно. К тому же, почитав введение в FPGA, понял, что ничего сложного и не будет (ну по крайней мере для такой простой задачи).
Итак,
-- 100 ns signal generator
process(clk)
variable t:integer range 0 to 5 := 0;
begin
if rising_edge(clk) then
t := t + 1;
if t = 5 then
t := 0;
tact <= not tact;
end if;
if t = 2 then
tact <= not tact;
end if;
end if;
end process;
Затем нужно как-то отображать и управлять. У нас две величины — длина периода и длина импульса, так что на длину периода отведем 3 знакоместа (с учетом десятых), и на длину периода — 3.
shared variable period : integer range 0 to 1000 := 500;
shared variable duty : integer range 0 to 1000 := 250;
shared variable dig1:std_logic_vector(3 downto 0):="0000";
shared variable dig2:std_logic_vector(3 downto 0):="0101";
shared variable dig3:std_logic_vector(3 downto 0):="0010";
shared variable di1:std_logic_vector(3 downto 0):="0000";
shared variable di2:std_logic_vector(3 downto 0):="0000";
shared variable di3:std_logic_vector(3 downto 0):="0101";
Ну для управления подойдут сигналы от кнопок, что видны внизу платы — их всего 4,
так что пусть две управляют изменением периода и импульса соответственно, одна задает знак изменения, а еще одна включает и отключает вывод генератора…
process(key1)
begin
if rising_edge(key1) then
ready <= not ready;
end if;
end process;
process(key3)
begin
if rising_edge(key3) then
if key4 = '1' then
inc_duty;
else
dec_duty;
end if;
end if;
end process;
process(key2)
begin
if rising_edge(key2) then
if key4 = '1' then
inc_period;
else
dec_period;
end if;
end if;
end process;
В управлении есть процедуры inc/dec, вот они
procedure inc_duty is
begin
if duty < period then
duty := duty + 1;
if dig1 = "1001" then
dig1 := "0000";
if dig2 = "1001" then
dig2 := "0000";
if dig3 = "1001" then
dig3 := "0000";
else
dig3 := dig3 + 1;
end if;
else
dig2 := dig2 + 1;
end if;
else
dig1 := dig1 + 1;
end if;
end if;
end procedure;
procedure dec_duty is
begin
if duty > 1 then
duty := duty - 1;
if dig1 = "0000" then
dig1 := "1001";
if dig2 = "0000" then
dig2 := "1001";
dig3 := dig3 - 1;
else
dig2 := dig2 - 1;
end if;
else
dig1 := dig1 - 1;
end if;
end if;
end procedure;
procedure inc_period is
begin
if period < 1000 then
period := period + 1;
if di1 = "1001" then
di1 := "0000";
if di2 = "1001" then
di2 := "0000";
if di3 = "1001" then
di3 := "0000";
else
di3 := di3 + 1;
end if;
else
di2 := di2 + 1;
end if;
else
di1 := di1 + 1;
end if;
end if;
end procedure;
procedure dec_period is
begin
if period > 1 then
period := period - 1;
if di1 = "0000" then
di1 := "1001";
if di2 = "0000" then
di2 := "1001";
if di3 = "0000" then
di3 := "1001";
else
di3 := di3 - 1;
end if;
else
di2 := di2 - 1;
end if;
else
di1 := di1 - 1;
end if;
end if;
end procedure;
Немного длинно и сложно (потому и свернуто), но вполне понятно.
Ну и надо как-то отображать — у нас семисегментный индикатор, и их 6 штук (вообще-то 8). Отображать будем время и, чтобы не мучится с точкой, в десятых микросекунды.
Пусть они по циклу переключаются и отображается текущая цифра:
process(tactX)
begin
case tactX is
when"000"=> en_xhdl<="11111110";
when"001"=> en_xhdl<="11111101";
when"010"=> en_xhdl<="11111011";
when"011"=> en_xhdl<="11110111";
when"100"=> en_xhdl<="11101111";
when"101"=> en_xhdl<="11011111";
when"110"=> en_xhdl<="10111111";
when"111"=> en_xhdl<="01111111";
when others => en_xhdl<="01111111";
end case;
end process;
process(en_xhdl)
begin
case en_xhdl is
when "11111110"=> data4<=dig1;
when "11111101"=> data4<=dig2;
when "11111011"=> data4<=dig3;
when "11110111"=> data4<="1111";
when "11101111"=> data4<=di1;
when "11011111"=> data4<=di2;
when "10111111"=> data4<=di3;
when "01111111"=> data4<="0000";
when others => data4<="1111";
end case;
end process;
process(data4)
begin
case data4 is
WHEN "0000" =>
dataout_xhdl1 <= "11000000";
WHEN "0001" =>
dataout_xhdl1 <= "11111001";
WHEN "0010" =>
dataout_xhdl1 <= "10100100";
WHEN "0011" =>
dataout_xhdl1 <= "10110000";
WHEN "0100" =>
dataout_xhdl1 <= "10011001";
WHEN "0101" =>
dataout_xhdl1 <= "10010010";
WHEN "0110" =>
dataout_xhdl1 <= "10000010";
WHEN "0111" =>
dataout_xhdl1 <= "11111000";
WHEN "1000" =>
dataout_xhdl1 <= "10000000";
WHEN "1001" =>
dataout_xhdl1 <= "10010000";
WHEN OTHERS =>
dataout_xhdl1 <= "11111111";
END CASE;
END PROCESS;
Признаюсь честно часть кода утащил от исходников, что шли с платкой — уж очень здорово и понятно написано! en_xhdl — этот сигнал будет управлять какой индикатор включен в такте переключения, dataout_xhdl1 — этот сигнал включает светодиоды, ну а data4 временный регистр и хранит цифру.
Осталось написать сердце, которое все и считает — собственно генератор. Тут tactX — генератор отображения, а cnt — счетчик положения в импульсе. Ну и lin — сигнал собственно генератора.
process(tact)
variable cntX : integer range 0 to 1000 := 0;
variable cnt : integer range 0 to 1000 := 0;
begin
if rising_edge(tact) then
if cntX = 0 then
tactX <= tactX + 1;
end if;
cntX := cntX + 1;
if cnt > period then
cnt := 0;
else
cnt := cnt + 1;
end if;
if cnt = 0 then
lin <= '0';
elsif cnt = duty then
lin <= '1';
end if;
end if;
end process;
Ну вот, осталось вывести данные — это делается постоянно, так что должно располагатся в блоке параллельного выполнения.
cat_led <= dataout_xhdl1;
en_led <= en_xhdl;
led1 <= not ready;
out1 <= lin when ready = '1' else '0';
out2 <= not lin when ready = '1' else '0';
В конце слепить все процессы вместе — полученный файл Quarus Prime благосклонно принял, скомпилил, сообщил, что
Top-level Entity Name v12
Family MAX II
Device EPM240T100C5
Timing Models Final
Total logic elements 229 / 240 ( 95 % )
Total pins 29 / 80 ( 36 % )
Total virtual pins 0
UFM blocks 0 / 1 ( 0 % )
Остался самый нудный этап, хотя и полностью графический — назначить сигналам конкретные пины. И все — осталось залить все в устройство и проверить! Что интересно, удалось все же уложиться в 229 ячеек, так что осталось еще аж 11 штук — но в реальности почти все сожрал интерфейс — кнопки и отображение. Собственно генератор может быть уложен в несколько ячеек — у интела есть документ, где они описывают, как уложить в 1 LUT — ну конечно, без управления…
Так что отвечая на вопрос заголовка — нет, не сложно, если знаешь С или Ада и понимаешь, как работает цифровая электроника, и да, сложно, если нет представления о базовых вещах… По крайней мере, у меня процесс написания занял день, и я получил массу удовольствия как от процесса разработки, так и от функционирующего устройства! И сосед доволен :)
Комментарии (44)
Sdima1357
28.07.2019 18:23+11 Нет такой платы за 200, начинаются с 300 без индикаторов и кнопок. (MAX ii EPM240)
2 чем Вас таймер от stm32f103 не устраивал, за 100Р?oam2oam Автор
28.07.2019 18:28+11. Ну вот так мне повезло… К тому же сами чипы очень дешевы — рублей по 80 стоят, а то вдруг придется что-то на них делать…
2. Так тем и не устроила, что не FPGA! На ней же не освоишь программирование на VHDL…Sdima1357
28.07.2019 18:32-1К тому же сами чипы очень дешевы
У них всего 100 циклов программирования, если я не путаю.
На ней же не освоишь программирование на VHDL
Ну можно поспорить :)AlexanderS
28.07.2019 19:47+1У них всего 100 циклов программирования, если я не путаю.
Путаете. Вернее не путаете, но путаете. То есть что-то путает производитель. Мдя… В общем, всё запутано так, что лучше почитать вот здесь. Если кратно — 10'000 циклов она точно выдержит)
nckma
29.07.2019 11:56Убить макс: marsohod.org/11-blog/91-killmax
oam2oam Автор
29.07.2019 12:00так я и не понял — все же в доке пропустили три нолика? или что? и на сколько циклов перезаписи ориентироваться, кто-нибудь подскажет?
AlexanderS
28.07.2019 19:52+1Это где ж такие платки по 200р можно прикупить? Мне кажется, что вам кажется, что они столько стоят)
Модуль генерации корректно описан? В процессе tact по переднему фронту при старте тикнет счётчик cntX и вроде бы больше тикать никогда не будет.DanilinS
28.07.2019 20:05Автор похоже закупался тогда, когда доллар был по 6 рублей.
Сейчас аналогичная плата «EPM240 Training Board Altera MAX II CPLD» стоит в районе 1100 руб.Sdima1357
28.07.2019 21:29За эти деньги можно прикупить «Xilinx XC6SLX16 Spartan 6 FPGA development board 32Mb SDRAM» ~$18- $19
Вполне серьезная платаoam2oam Автор
29.07.2019 06:19Вот, кстати, если думать о будущем, хотелось бы перейти на что-то серьезное — но так, чтобы это стоило потом применять в проектах…
Однако выбор довольно большой — что же делать?AlexanderS
29.07.2019 15:33Тут надо понимать, что ПЛИС — это просто матрица соединений и всё. Да, во «взрослых» ПЛИСах есть всякие штуки типа PLL, или DSS. Но там нет ЦАП и АЦП, энергонезависимой памяти, готовых интерфейсов типа UART/SPI/I2C (на самом деле через IP Core всё добывается, но ядра денег стоят) и прочего. Если вы захотите реагировать на уровень чего-то, то вам придётся ставить микросхему АЦП и реализовать интерфейс с ней. Если понадобиться формировать уровень — то же самое с ЦАП. Хранить калибровочные параметры или какие-то данные? Ставьте свою ПРОМ. Получается куча мороки вместо того, чтобы сразу использовать какой-нибудь микроконтроллер. ПЛИС хорош для быстрой обработки сигналов, но, как практика показывает, их сфера применения «в быту» очень узка. Иногда, бывает нужна надежность. Чтобы контроллер не завис, не сбойнул и не сформировал какую-нибудь бяку из-за «поехавшей» в памяти программы. В этом плане ПЛИС неплох, т.к. по своей сути представляет цифровую схему из набора регистров — тут логике сложно сломаться.
Я, уже не помню для чего, как-то использовал Digilent 410-328. Это плата в форм-факторе DIP-48 на основе Xilinx Artix-7. Плата уже имеет на борту конфигурационную память, распаянный программатор и немного SDRAM. Помню, что туда без проблем влезли все интерфейсные модули (два протокола UART + SPI) и места ещё осталось достаточно. 10 тыс. лутов, конечно, гораздо лучше, чем 240 ячеек в MAX II, но и цена платы сильно побольше будет)NetNazgul
29.07.2019 16:02+1Мы тут как-то по работе недавно покупали Zynq-модули у Trenz, на выходе получается ARM-процессор + ПЛИС довольно большой ёмкости (это в одном кристалле), + SPI Flash + пара модулей на плате, выводы и отдельный Xilinx-совместимый программатор на CPLD от Lattice, и всё это чуть меньше $100.
AlexanderS
29.07.2019 16:23С этой серией в живую я напрямую не сталкивался. Философия этой платформы вроде как раз в том, чтобы в ПЛИСе реализовать то, что в МК не сделать (какую-нибудь специфичный высокоскоростной интерфейс, например, или БПФ), а всё остальное — отдать обычным программистам, благо на ARM ядро хоть Debian ставь. По логике это должно увеличивать сроки разработки.
NetNazgul
30.07.2019 08:45Ну там никто не мешает и как просто ПЛИС без процессора использовать на самом деле.
Но вообще — позволяет выполнить и интегрировать realtime-периферию гораздо ближе к ядру процессора, чем при отдельных кристаллах, не ограничиваясь при этом softcore-процессором внутри самой ПЛИС.
mpa4b
29.07.2019 16:40Забавно, что именно в max II как раз таки есть энергонезависимая память, в виде юзерского кусочка флеш.
AlexanderS
29.07.2019 19:55А еще для неё не нужно внешней конфигурационной памяти)
oam2oam Автор
29.07.2019 19:58А вот, кстати, как осуществляется защита IP в fpga с внешней конфигурационной памятью? В MAX II это просто…
AlexanderS
29.07.2019 21:09Во флешке лежит bitstream же, который получен с помощью САПР. Можно взять эту прошивку и залить в другой такой же ПЛИС — работать будет. Вытащить прошивку можно, но это надо чтобы вот прям очень нужно было реверсить железо для неё. Да и сам разработчик заранее может озаботиться шифрованием обмена памяти с ПЛИСом. Ну а как ведётся сам по себе учёт IP — это уже вопросы ценовой политики. IP стоят ооочень дорого)
Andy_Big
30.07.2019 18:29Да и сам разработчик заранее может озаботиться шифрованием обмена памяти с ПЛИСом
Протокол загрузки конфигурации в ПЛИС строго регламентирован и не допускает никаких изменений со стороны разработчика. Так что да — посадив сниффер на линии загрузки ПЛИС можно легко и безболезненно получить «прошивку» ПЛИС.
Есть ПЛИС, поддерживающие шифрование конфигурации, но они более дорогие и для них нужны более дорогие загрузочные флэшки, которые так же поддерживают шифрование.AlexanderS
30.07.2019 20:09Да, именно так. Мало того, частичку ПЛИСа ещё нужно и постоянным питанием обеспечивать чтобы не утратился ключ в энергозависимой памяти. Ставятся батарейка или аккумулятор. Но тут нюанс в том, что нужно заранее продумать схемотехнику замены этого аккумулятора — ставят большие кондёры или ионистор, чтобы при техобслуживании через N-лет можно было быстренько заменить источник питания. Конечно, так заморачиваются только когда реально интеллектуальная собственность в прошивке очень дорогая.
Andy_Big
30.07.2019 20:40частичку ПЛИСа ещё нужно и постоянным питанием обеспечивать чтобы не утратился ключ в энергозависимой памяти
Этого я не знал :) Думал, что ключ прошивается в какую-нибудь OTP-область :)
nerudo
31.07.2019 08:59Не обязательно, бывает однократно программируемый энергонезависимый ключ.
AlexanderS
31.07.2019 14:19Я сам лично этот вопрос не изучал — коллеги по работе в каком-то проекте заморачивались, знаю как у них реализовывалось.
Однократно программируемый ключ можно же вытащить? Область наверняка заранее определена: электронный микроскоп + знание топологии ПЛИСа…nerudo
31.07.2019 14:28Вероятно можно, у нас тут специалист даже был BarsMonster но это переводит задачу на совсем другой уровень…
hhba
31.07.2019 08:56Иногда, бывает нужна надежность. Чтобы контроллер не завис, не сбойнул и не сформировал какую-нибудь бяку из-за «поехавшей» в памяти программы. В этом плане ПЛИС неплох, т.к. по своей сути представляет цифровую схему из набора регистров
А эта цифровая схема из набора регистров в случае fpga чем конфигурируется, как вы думаете?
AlexanderS
31.07.2019 14:33Конфигурируется-то она из ПРОМки, но если будет косяк в данных, то процедура инициализации не завершится (CRC не сойдётся) и ПЛИС не загрузится.
hhba
31.07.2019 15:19Ненене, я не про то, из чего она конфигурируется, а про то, где лежат параметры конфигурации в загруженной fpga.
AlexanderS
31.07.2019 23:02Ааа, вы про конфигурирование макроячеек и матрицы соединений? В принципе там такие же транзисторы как и везде)) Технологическая разница в изготовлении может как-то сказываться? Ну… это вопрос интересный. Надо бы на досуге поизучать.
hhba
31.07.2019 23:26Да, я про макроячейки, но не про технологическую разницу, а про SRAM-based FPGA (то есть примерно все коммерческие СБИС, исключая CPLD-мелочь и космические антифузы). Там самые натуральные ячейки памяти, которые собственно и конфигурируют макроячейки. И если в ячейку памяти что-то этакое попадет… Уж явно в этой ситуации у ПЛИС нет никакого преимущества перед такой же ячейкой памяти в микроконтроллере.
Pyhesty
29.07.2019 01:32Я извиняюсь, не пишу на vhdl, но зная архитектуру ПЛИС предположу, что у вас должны наблюдаться гличи на сигнале генератора, так как выходной сингал не простробирован регистром…
Единственный раз, когда пришлось работать с vhdl, намучился с переменными (когда частота подросла) и все перевел на сигналы, сигналы это физически регистр.
А в целом отлично, что есть доступные для изучения комплекты, спасибо за статью
azudem
29.07.2019 04:59Во-первых, эстетически больно смотреть на отступы вашем коде, не говоря о неожиданных сменах регистра и именах.
Во-вторых, считывать кнопки по фронту не совсем хорошая идея из-за дребезга, особенно в случае дешёвых китайских кнопок. Обычно просто сэмплят с малой частотой, 50-100 Гц.oam2oam Автор
29.07.2019 06:26Я тоже хотел сначала устранить дребезг и добавить автоизменение (ну для этого надо просто процесс с частотой 2-5 герц) — но тогда не входит по размеру, а как оптимизировать не придумал, к сожалению.
VioletGiraffe
29.07.2019 10:07А по-моему, очень даже неплохое и удобочитаемое форматирование. Сам бы я делал чуть по-другому, но вариант автора мне нравится и более чем имеет право на жизнь, ИМХО.
VioletGiraffe
29.07.2019 10:12Спасибо за заметку! Смахнул скупую слезу, вспомнив, как в универе писал на VHDL железки для отладочной платы Spartan-3E.
А посоветуйте, пожалуйста, какую отладочную плату FPGA / CPLD взять за не очень дорого, скажем, до $70, чтобы восстановить навык, ну и делать простые штуки, подобные описанной в статье? Лучше, наверное, FPGA? Есть идея подключить быстрый АЦП и попытаться сделать простой осциллограф.AlexanderS
29.07.2019 15:43Можно на e-bay взять плату с Virtex4. XC4VLX100 вам не только для осциллографа хватит, поверьте) Но готовьтесь к тому, что нужно будет купить не особо дешёвый JTAG программатор. Либо же взять какую-нибудь отладочную плату на Spartan 6, выше название где-то приводили. Я в своё время маялся вот такой ерундой на марсоходе)
NetNazgul
29.07.2019 13:42+2Добро пожаловать в мир HDL! Несколько придирок, на которые хотелось бы обратить внимание. :)
1. Строго говоря, код на HDL не является «программой». На это же напрямую намекает само название hardware description language — «язык описания аппаратуры». HDL скорее ближе к схемотехнике, чем к программированию. Хотя конечно ПЛИС дают некоторое впечатление программирования, т.к. результат можно увидеть буквально через пару минут, а не через три месяца как в случае со СБИС.
2. Первый процесс даст вам деление на 6, а не на 5 (отсчёты с 0 до 5, 0-1-2-3-4-5).
3. Никогда раньше не сталкивался с
, в любом случае даже просто переменные использовать в VHDL лучше по минимуму и для промежуточных значений в процессах; основу, а тем более 100% выходов синхронных элементов, должны составлять сигналыshared variable
. Если возникают сложности с использованием значений сигналов внутри процессов — следует подробнее ознакомиться к параллельными и последовательными блоками в VHDL (в Verilog логика похожа) и присвоением значений сигналам и переменным.signal
4. С т.з. пользователя управление с требованием зажатия нескольких кнопок выглядит не очень приятным. Я бы предложил, например, отвести одну кнопку под выбор режима (редактирование одного или другого параметра), две под инкремент/декремент и одну под включение/выключение как есть.
5. Если уж мы выполняем проверку «длина импульса должна быть меньше периода» при изменении импульса, то стоило бы проверять и обратный случай при изменении периода.
6. В целом получается, что duty/period и dig/di — это живущие своей независимой жизнью одинаковые величины, отличающиеся лишь в представлении. В общем случае более логично было бы выполнять двоично-десятичное преобразование от исходных значений. Но в данном примере подозреваю, что стоял вопрос жёсткого ограничения по площади ПЛИС.
7.if cnt > period then
elsif cnt = duty then
Здесь опять «ошибка +1» — счётчик работает на диапазоне от 0 до периода, что приводит к заданию числом period реального значения в period+1 тактов.
8.
variable cntX : integer range 0 to 1000 := 0; variable cnt : integer range 0 to 1000 := 0;
Тут опять максимальное значение 1000, что даст нам 1001 отсчётов, а еще — возможно данное описание корректно работает с обработкой переполнения счётчика (переход из 1000 в 0), но описано несколько страшновато. В самом первом процессе счётчик более корректен — там данная проверка описания переполнения выполняется явно:
t := t + 1; if t = 5 then t := 0; tact <= not tact; end if;
… и тут я понял, что пункт №2 как раз правильный, а у меня нет, всё дело в переменных, где сначала выполняется t := t + 1, получаем 5, а потом сразу меняем на 0. Но такое использование переменных может привести к избыточной логике. При использовании сигналов, новое значение устанавливается в сигнале только по выходу из процесса, т.о. если процесс сихронный, то результат t <= t + 1 мы «увидим» в этом процессе только на следующем такте (так же это работает и в реальности для триггеров). Совсем хорошо было бы так:
signal t: integer; process(clk) begin if rising_edge(clk) then if t = 4 then t <= 0; else t <= t + 1; end if; end if; end process;
Возвращаясь к счётчикам — для cntX переход через 0 не проверяется, а для cnt, как уже выше было сказано, посчитается как +1.oam2oam Автор
29.07.2019 13:55Спасибо за конструктивную критику! Буду думать… Правда, не совсем понимаю, почему нельзя использовать shared variable — они же есть в языке, их использование оговорено, вроде я по уставу их использовал. Ну а с кнопками — как уж вышло, я уже выше отвечал, что хотелось бы большего, но LUTы не позволяют…
NetNazgul
29.07.2019 15:58+1Variable, используемые как де-факто регистры, — это один из верных способов выстрелить себе в ногу в VHDL. Их поведение сложнее контролировать в асинхронных (описывающих логику, а не триггер) процессах, в целом есть довольно высокий шанс получить на выходе т.н. защёлки (latch), которых не должно быть в проекте, если только не ставишь их туда вручную с конкретной целью.
Опять же, разные инструменты синтеза дружат с переменными очень по-разному и могут создавать из них регистры даже там, где это не требуется, после чего схема работает неправильно.oam2oam Автор
29.07.2019 16:32так у меня, как я думал, все синхронное; видимо, поэтому не ощутил проблем. А так, как подумаешь — как эти разработчики под ПЛИС думают — что-то очень уж сложное выходит.
AlexanderS
29.07.2019 20:55+2Просто когда пишется код на HDL надо понимать, что каждая строчка кода отрабатывает каждый такт. Всё работает параллельно и результаты этой работы будут доступны ровно через такт — обновляются сигналы одновременно во всём коде. Те, кто приходят из Си мыслят в цикле последовательном исполнении команд и переключиться сразу на параллельное мышление нет так просто как кажется на первый взгляд. Поэтому новички обязательно вкорячивают переменные в код, где они просто даром не нужны.
Это не значит, что переменные вообще использовать не надо. В процедурах и функциях — там можно использовать их «как обычно». Но надо чётко понимать к чему это приводит на схемотехническом уровне в реальной схеме.
Например надо подсчитывать чётность передаваемого байта в потоке данных. Мы ограничены во времени — на всё отводится один такт (потоковая передача). Можно сделать это так:
process(CLK) begin if (CLK'event) and (CLK = '1') then Do <= Di; PAR <= Di(7) xor Di(6) xor Di(5) xor Di(4) xor Di(3) xor Di(2) xor Di(1) xor Di(0); end process;
А можно сделать так:
process(CLK) variable parity : std_logic := '0'; begin if (CLK'event) and (CLK = '1') then Do <= Di; parity := '0'; for i in 0 to 7 loop parity := parity xor Di(i); end loop; PAR <= parity; end process;
Схемотехнически результат этих двух примеров кода будет один и тот же — логика XOR соответствующих бит между выходами регистра Di и входом регистра PAR. Верхняя запись смотрится лаконичнее и красивее, чем нижняя, но это только для байта. А если на входе будет 64-разрядное слово? Уже целесообразнее нижняя форма записи чисто из-за экономии времени и нежелания делать тупую работу, расписывая все 64 бита)
В целях обучения я бы на вашем месте не использовал переменные в синтезируемом коде. Кроме того, я бы сделал полностью синхронную схему, работающую по единому тактовому сигналу без асинхронных элементов и межтактовых переходов. К сожалению, сложно объяснить всю опасность «ездящей» времянки из-за асинхронных вещей — это хорошо доходит до людей только после этак недельки интенсивной отладки большого проекта, когда то работает, то нет) Опять же надо понимать, что и асинхронные схемы вполне можно использовать, но разработчики со стажем прибегают к этому обычно по веским причинам и когда деваться некуда.Andy_Big
30.07.2019 18:34Те, кто приходят из Си мыслят в цикле последовательном исполнении команд и переключиться сразу на параллельное мышление нет так просто как кажется на первый взгляд.
Вот это да, самое большое затруднение для программистов при вхождении в ПЛИС. Сам с этим помучился пока не переключился :)
Dmitriy0111
Данный этап можно также сделать в удобном для Вас текстовом редакторе, если правильно описать .qsf файл. Да и в целом можно вообще не запускать графическое представление Quartus. Например, как здесь github.com/Dmitriy0111/wrapper_for_8bitworkshop через makefile.