Где-то пол года назад решил попробывать свои силы в программировании ПЛИС. До этого со схемотехникой никогда не сталкивался. Был небольшой опыт использования микроконтроллеров (Atmega328p, STM32). Сразу после решения освоиться с ПЛИС, встал вопрос выбора языка, который я буду использовать. Выбор пал на VHDL из-за его строгой типизации. Мне, как новичку, хотелось как можно больше возможных проблем отловить на этапе синтеза, а не на рабочем устройстве.
Почему именно семисегментный дисплей? Мигать светодиодом уже надоело, да и логика мигания им не представляет из себя ничего интересного. Логика управления дисплеем с одной стороны сложнее, чем мигание светодиодом (т. е. писать ее интереснее), а с другой достаточно простая в реализации.
Что я использовал в процессе создания устройства:
- ПЛИС Altera Cyclone II (знаю, что он безнадежно устарел, зато у китайцев его можно купить за копейки)
- Quartus II версии 13.0.0 (на сколько я знаю это последняя версия поддерживающая Cyclone II)
- Симулятор ModelSim
- Семисегментный дисплей со сдвиговым регистром
Задача
Создать устройство, которое будет в цикле показывать числа 0 — 9. Раз в секунду отображаемое на дисплее значение должно увеличиваться на 1.
Реализовать данную логику можно по-разному. Я разделю данное устройство на модули, каждый из которых будет выполнять какое-то действие и результат этого действия будет передаваться следующему модулю.
Модули
- Данное устройство должно уметь отсчитывать время. Для подсчета времени я создал модуль «delay». Этот модуль имеет 1 входящий и 1 исходящий сигнал. Модуль принимает частотный сигнал ПЛИС и, через указанное количество периодов входящего сигнала, меняет значение исходящего сигнала на противоположное.
- Устройство должно считать от 0 до 9. Для этого будет использоваться модуль bcd_counter.
- Для того, чтобы зажечь сегмент на дисплее, нужно выставить в сдвиговом регистре дисплея соответствующий сегменту бит в 0, а для того, чтобы погасить сегмент в бит нужно записать 1 (мой дисплей имеет инвертированную логику). Установкой и сбросом нужных битов будет заниматься декодер bcd_2_7seg.
- За передачу данных будет отвечать модуль transmitter.
Главное устройство будет управлять корректной передачей сигналов между модулями, а также генерировать сигнал rclk по завершению передачи данных.
Как видно из схемы устройство имеет 1 входящий сигнал (clk) и 3 исходящих сигнала (sclk, dio, rclk). Сигнал clk приходит в 2 делителя сигнала (sec_delay и transfer_delay). Из устройства sec_delay выходит исходящий сигнал с периодом 1с. По переднему фронту этого сигнала счетчик (bcd_counter1) начинает генерировать следующее число для отображения на дисплее. После того, как число сгенерировано, декодер (bcd_2_7seg1) преобразует двоичное представление числа в горящие и не горящие сегменты на дисплее. Которые, с помощью передатчика (transmitter1), передаются на дисплей. Тактирование передатчика осуществляется с помощью устройства transfer_delay.
Код
Для создания устройства в VHDL используется конструкция из двух составляющих entity и architecture. В entity декларируется интерфейс для работы с устройством. В architecture описывается логика работы устройства.
entity delay is
-- При объявлении entity, поле generic не является обязательным
generic (delay_cnt: integer);
-- Описываем входные и выходные сигналы устройства
port(clk: in std_logic; out_s: out std_logic := '0');
end entity delay;
Через поле generic мы можем задать устройству нужную задержку. А в поле ports описываем входящие и исходящие сигналы устройства.
-- В секции architecture описывается то, как устройство будет работать
-- С одной entity может быть связано 0 или более архитектур
architecture delay_arch of delay is
begin
delay_proc: process(clk)
variable clk_cnt: integer range 0 to delay_cnt := 0;
variable out_v: std_logic := '0';
begin
-- Если имеем дело с передним фронтом сигнала
if(rising_edge(clk)) then
clk_cnt := clk_cnt + 1;
if(clk_cnt >= delay_cnt) then
-- switch/case в языке VHDL
case out_v is
when '0' =>
out_v := '1';
when others =>
out_v := '0';
end case;
clk_cnt := 0;
-- Устанавливаем в сигнал out_s значение переменной out_v
out_s <= out_v;
end if;
end if;
end process delay_proc;
end delay_arch;
Код внутри секции process исполняется последовательно, любой другой код исполняется параллельно. В скобках, после ключевого слова process указываются сигналы, по изменению которых данный процесс будет запускаться (sensivity list).
Устройство bcd_counter в плане логики выполнения идентично устройству delay. Поэтому на нем я подробно останавливаться не буду.
entity bcd_to_7seg is
port(bcd: in std_logic_vector(3 downto 0) := X"0";
disp_out: out std_logic_vector(7 downto 0) := X"00");
end entity bcd_to_7seg;
architecture bcd_to_7seg_arch of bcd_to_7seg is
signal not_bcd_s: std_logic_vector(3 downto 0) := X"0";
begin
not_bcd_s <= not bcd;
disp_out(7) <= (bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or
(not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1)
and bcd(0));
disp_out(6) <= (bcd(2) and not_bcd_s(1) and bcd(0)) or
(bcd(2) and bcd(1) and not_bcd_s(0));
disp_out(5) <= not_bcd_s(2) and bcd(1) and not_bcd_s(0);
disp_out(4) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1)
and bcd(0)) or
(bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or
(bcd(2) and bcd(1) and bcd(0));
disp_out(3) <= (bcd(2) and not_bcd_s(1)) or bcd(0);
disp_out(2) <= (not_bcd_s(3) and not_bcd_s(2) and bcd(0)) or
(not_bcd_s(3) and not_bcd_s(2) and bcd(1)) or
(bcd(1) and bcd(0));
disp_out(1) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1)) or
(bcd(2) and bcd(1) and bcd(0));
disp_out(0) <= '1';
end bcd_to_7seg_arch;
Вся логика данного устройства выполняется параллельно. О том как получить формулы для данного устройства я рассказывал в одном из видео на своем канале. Кому интересно, вот ссылка на видео.
entity transmitter is
port(enable: in boolean;
clk: in std_logic;
digit_pos: in std_logic_vector(7 downto 0) := X"00";
digit: in std_logic_vector(7 downto 0) := X"00";
sclk, dio: out std_logic := '0';
ready: buffer boolean := true);
end entity transmitter;
architecture transmitter_arch of transmitter is
constant max_int: integer := 16;
begin
sclk <= clk when not ready else '0';
send_proc: process(clk, enable, ready)
variable dio_cnt_v: integer range 0 to max_int := 0;
variable data_v: std_logic_vector((max_int - 1) downto 0);
begin
-- Установка сигнала dio происходит по заднему фронту сигнала clk
if(falling_edge(clk) and (enable or not ready)) then
if(dio_cnt_v = 0) then
-- Прежде всего передаем данные, потом позицию на дисплее
-- Нулевой бит данных идет в нулевой бит объединенного вектора
data_v := digit_pos & digit;
ready <= false;
end if;
if(dio_cnt_v = max_int) then
dio_cnt_v := 0;
ready <= true;
dio <= '0';
else
dio <= data_v(dio_cnt_v);
dio_cnt_v := dio_cnt_v + 1;
end if;
end if;
end process send_proc;
end transmitter_arch;
В сигнал sclk я перенаправляю значение входящего в передатчик сигнала clk, но только в том случае, если устройство в данный момент выполняет передачу данных (сигнал ready = false). В противном случае значение сигнала sclk будет равно 0. В начале передачи данных (сигнал enable = true), я объединяю данные из двух входящих в устройство 8-и битных векторов (digit_pos и digit) в 16-и битный вектор (data_v) и передаю данные из этого вектора по одному биту за такт, устанавливая значение передаваемого бита в исходящий сигнал dio. Из интересного в этом устройстве хочу отметить то, что данные в dio устанавливаются на задний фронт сигнала clk, а в сдвиговый регистр дисплея данные с пина dio будут записаны по приходу переднего фронта сигнала sclk. По завершению передачи, установкой сигнала ready <= true сигнализирую другим устройствам, что передача завершилась.
entity display is
port(clk: in std_logic; sclk, rclk, dio: out std_logic := '0');
end entity display;
architecture display_arch of display is
component delay is
generic (delay_cnt: integer);
port(clk: in std_logic; out_s: out std_logic := '0');
end component;
component bcd_counter is
port(clk: in std_logic; bcd: out std_logic_vector(3 downto 0));
end component;
component bcd_to_7seg is
port(bcd: in std_logic_vector(3 downto 0);
disp_out: out std_logic_vector(7 downto 0));
end component;
component transmitter is
port(enable: in boolean;
clk: in std_logic;
digit_pos: in std_logic_vector(7 downto 0);
digit: in std_logic_vector(7 downto 0);
sclk, dio: out std_logic;
ready: buffer boolean);
end component;
signal sec_s: std_logic := '0';
signal bcd_counter_s: std_logic_vector(3 downto 0) := X"0";
signal disp_out_s: std_logic_vector(7 downto 0) := X"00";
signal tr_enable_s: boolean;
signal tr_ready_s: boolean;
signal tr_data_s: std_logic_vector(7 downto 0) := X"00";
-- Этот флаг, совместно с tr_ready_s контролирует
-- установку и сброс rclk сигнала
signal disp_refresh_s: boolean;
signal transfer_clk: std_logic := '0';
begin
sec_delay: delay generic map(25_000_000)
port map(clk, sec_s);
transfer_delay: delay generic map(10)
port map(clk, transfer_clk);
bcd_counter1: bcd_counter port map(sec_s, bcd_counter_s);
bcd_to_7seg1: bcd_to_7seg port map(bcd_counter_s, disp_out_s);
transmitter1: transmitter port map(tr_enable_s,
transfer_clk,
X"10",
tr_data_s,
sclk,
dio,
tr_ready_s);
tr_proc: process(transfer_clk)
variable prev_disp: std_logic_vector(7 downto 0);
variable rclk_v: std_logic := '0';
begin
if(rising_edge(transfer_clk)) then
-- Если передатчик готов к передаче следующей порции данных
if(tr_ready_s) then
-- Если передаваемые данные не были только что переданы
if(not (prev_disp = disp_out_s)) then
prev_disp := disp_out_s;
-- Помещаем передаваемые данные в шину данных передатчика
tr_data_s <= disp_out_s;
-- Запускаем передачу данных
tr_enable_s <= true;
end if;
else
disp_refresh_s <= true;
-- Флаг запуска передачи данных нужно снять
-- до завершения передачи,
-- поэтому снимаю его по приходу следующего частотного сигнала
tr_enable_s <= false;
end if;
if(rclk_v = '1') then
disp_refresh_s <= false;
end if;
if(tr_ready_s and disp_refresh_s) then
rclk_v := '1';
else
rclk_v := '0';
end if;
rclk <= rclk_v;
end if;
end process tr_proc;
end display_arch;
Это устройство управляет другими устройствами. Здесь, перед объявлением вспомогательных сигналов, я объявляю компоненты которые буду использовать. В самой архитектуре (после ключевого слова begin) я создаю экземпляры устройств:
- sec_delay — экземпляр компонента delay. Исходящий сигнал направляется в сигнал sec_s.
- transfer_delay — экземпляр компонента delay. Исходящий сигнал направляется в сигнал transfer_clk.
- bcd_counter1 — экземпляр компонента bcd_counter. Исходящий сигнал направляется в сигнал bcd_counter_s.
- bcd_to_7seg1 — экземпляр компонента bcd_to_7seg. Исходящий сигнал направляется в сигнал disp_out_s.
- transmitter1 — экземпляр компонента transmitter. Исходящие сигналы направляются в сигналы sclk, dio, tr_ready_s.
После экземпляров компонентов объявляется процесс. Этот процесс решает несколько задач:
- Если передатчик не занят, то процесс инициализирует начало передачи данных
if(tr_ready_s) then if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s; -- Помещаем передаваемые данные в -- шину данных передатчика tr_data_s <= disp_out_s; -- Запускаем передачу данных tr_enable_s <= true; end if; else ...
- Если передатчик занят (tr_ready_s = false), то процесс устанавливает значение сигнала disp_refresh_s <= true (этот сигнал обозначает, что по завершении передачи нужно обновить данные на дисплее). Также устанавливается значение сигнала tr_enable_s <= false, если этого не сделать до завершения передачи, то загруженные в передатчик данные будут переданы повторно
- Устанавливает и сбрасывает сигнал rclk после завершения передачи данных
if(rclk_v = '1') then disp_refresh_s <= false; end if; if(tr_ready_s and disp_refresh_s) then rclk_v := '1'; else rclk_v := '0'; end if; rclk <= rclk_v;
Временная диаграмма
Сначала передаются данные “10011111“. Затем передается позиция числа на дисплее “00010000“ (этот параметр приходит в передатчик, как константа X”10”). В обоих случаях первым передается крайний правый бит (lsb).
Весь код можно посмотреть на github. Файлы с припиской *_tb.vhd — это отладочные файлы для соответствующих компонентов (например transmitter_tb.vhd — отладочный файл для передатчика). Их я на всякий случай тоже залил на github. Данный код был загружен и работал на реальной плате. Кому интересно, иллюстрацию работы кода можно посмотреть вот тут (начиная с 15:30). Спасибо за внимание.
Комментарии (25)
Alex_ME
10.04.2019 00:29+1Я совсем дилетант в верилоге, и меня всегда интересовало слелдующее: насколько оптимально то, что генерируется по Verilog коду? Например, делитель — двоичный счетчик, последовательность триггеров. А код автора код практически как на обычных императивных языках. Что получится в итоге? Поэтому есть ряд вопросов:
- Насколько правильно так вообще писать?
- Какие есть правила написания эффективного verilog-кода?
Andy_Big
10.04.2019 02:11Оптимальность результата зависит от того насколько правильно программист понимает что получится в результате синтеза его кода.
По делителю: вот эти строчки:
generic (delay_cnt: integer); ... variable clk_cnt: integer range 0 to delay_cnt := 0; ... if(rising_edge(clk)) then clk_cnt := clk_cnt + 1;
фактически и являются цепочкой последовательно соединенных триггеров в количестве integer (если не ошибаюсь, это 32), первый из которых «щелкает» по фронту clk :) Точнее, первые две строчки синтезируют цепочку триггеров, а две последние подключают к первому из этих триггеров сигнал клока. Синтезатору дано в коде прямое указание на это, никак иначе он трактовать и синтезировать не сможет.
Фактически, если понимаешь что делаешь, то VHDL, Verilog — это ассемблер для FPGA. У каждого кода будет вполне предсказуемый результат синтеза. Но если не очень понимаешь, то запросто пройдешься по массе способов отстрелить себе обе ноги :)
Что касается оптимальности по быстродействию и объему, то тут уже зависит не только от искусства программирования, но и от знания архитектуры конкретной FPGA, от умения правильно вручную расположить разные блоки на «карте» чипа, распределить клоки, задействовать аппаратные возможности и т.п.AKudinov
10.04.2019 08:27Не соглашусь с Вашим описанием счётчика.
То, о чём Вы говорите (цепочка триггеров, на первый из которых подан тактовый сигнал) — это Асинхронный счётчик, или счётчик пульсаций, где каждый последующий бит тактируется предыдущим, и просто щёлкает в два раза реже. Его огромный недостаток — состоит в том, что биты на выходе встают в новое состояние последовательно, а не одновременно. Синтезировать такую конструкцию можно, видимо, только используя generate. А потом к ней ещё констрейны писАть, чтобы синтезатор знал, как тактировать триггеры…
У автора же описан обычный синхронный счётчик, т.е. все биты clk_cnt затактированы сигналом clk. А к выходу регистра подключена асинхронная логика, вычисляющая "+1", и подающая на его же вход. А дальше — цифровой компаратор. На мой взгляд, оптимальнее было бы поставить компаратор на строгое равенство: он проще, меньше и быстрее, а значения счётчик всё равно перебирает последовательно.
asmolenskiy
10.04.2019 06:07Отимальнее, только собирать из макроблоков, но с таким кодом тяжело работать людям непосвященным. Я как-то таким образом написал автомат тренинга DDR (тюнинг элементов задержки на буферах) процентов на 70. Пришлось — так как иначе просто не растаскивалось по таймингам. Этот код люди даже открывать боятся, не то что править. Благо работает.
Еще оптимальность зависит от особенностей FPGA
Пример 1:
Есть такое понятие как Unique Controlling Set. Обычно это набор сигналов Set/Reset/Clock. Этот набор заходит в логический блок и все триггеры внутри него управляются этим уникальным набором и никаким другим. То есть логика @(posedge CLK) и логика @(posedge CKL or negedge nRST) обречены располагаться в разных блоках. Один несчастный регистр с уникальным набором сожрет целую ячейку со всем ее фаршем. Количество уникальных наборов прямо влияет на оптимальность размещения и утилизацию. На больших проектах оптимизация управляющих наборов дает весьма заметные результаты.
Пример 2.
ЛОгические генераторы в FPGA Xilinx имеют 6 входов. Соотвественно логика A <= C1 | C2 & C3 & C4 | C5 & C6 — займет один логический генератор, а когда туда добавляется C7 — скорее всего три генератора и два уровня логики. А логика с двумя условиями будет иметь ту же утилизацию, что и с 6ю. Понимая это можно оптимизировать код.
Пример 3.
Блочная память. Количество элементов блочной памяти обычно ограничено как по количеству так и по расположению в ПЛИС. Если бездумно жрать её не обращая внимания на размерность блоков и их особенности — закончиться она очень быстро и размещаться будет очень не оптимально.
Пример 4
***
сотни их. Мораль в том что надо читать даташиты на ПЛИСы.
asmolenskiy
10.04.2019 06:41Еще кстати есть такой момент как энергоэффективность.
Чтобы ПЛИС поменьше жрала — нужно:
1. Все что имеет входы EN — держать под EN только когда надо.
2. Писать логику так, чтобы регистры не переключались просто так, то есть чтобы все имели какой-то «if». Так как потребление в основном обусловленно тем — каким током и как часто внутренние блочки заряжают друг другу входную емкость в момент смены своих состояний. Ну и от SLEW_RATE настроек выходных каскадов — смысл тот же, просто емкость снаружи по отношению к ПЛИС.
lingvo
10.04.2019 11:57Вы можете посмотреть реальное количество потребленных слайсов, лутов, памяти и DSP блоков после синтеза и разводки дизайна.
Например bcd_to_7seg синтезатор наверняка зафигачит, как look-up-table, которую вы можете получить и с помощью LUT и используя block ram. При этом в исходном коде я бы тоже написал тупо таблицу, а не AND/OR.
oam2oam
10.04.2019 04:19Как говорится, аналогичная история, но с MAX II (а на ней всего 240 ячеек)… Полгода назад тоже заинтересовался темой ПЛИС, под руки попал сей девайс с 8 семисегментными индикаторами и 4 кнопками, а сосед попросил сделать управляемый генератор сигналов — ну там чтобы можно было задавать (и менять) длительность сигнала и его скважность. Ну я и сделал… в 237 ячеек вошло вместе с управлением — 4 индикатора показывали, а две кнопки управляли увеличением и уменьшением, периодом в микросекундах, в 4 остальные и еще 2 кнопки — длительностью «1».
Но надо сказать, что я подошел к решению управления индикатором совершенно не так, как автор… Может быть потому, что у меня сразу этих индикаторов было 8. Ну и еще я совершенный нуб в ПЛИС… :)) Да, кстати, писал тоже на VHDL, но просто потому, что отлично знаю язык Ада, а VHDL — это она практически (и даже на основе ее создан).
Я сделал все в 3 процессах:
один управляет сигналом выбора индикатора
process(tactX) begin case tactX is when"000"=> en_xhdl<="11111110"; when"001"=> en_xhdl<="11111101"; ...
второй управляет тем, что отображать
process(en_xhdl) begin case en_xhdl is when "11111110"=> data4<=dig1; when "11111101"=> data4<=dig2; when "11111011"=> data4<=dig3; ...
ну а третий управляет сегментами
process(data4) begin case data4 is WHEN "0000" => dataout_xhdl1 <= "11000000"; WHEN "0001" => dataout_xhdl1 <= "11111001"; WHEN "0010" => dataout_xhdl1 <= "10100100"; ...
ну и конечно, сам генератор примерно как у автора, а остальное — сервис управления и вывода прямого и комплиментарного сигналов.
Но автор большой молодец — как-то у него все правильно и красиво, с диаграммами!
sim2q
10.04.2019 22:50Мечтаю сделать полностью аппаратный PWM контроллер для кулера. На вход ему кол-во оборотов + последовательный вход с тахометра, на выходе — PWM на pin для мотора.
asmolenskiy
11.04.2019 00:06-1Это не полностью аппаратный, так как кто-то же должен количество оборотов задать.
Полностью аппаратный — это когда на вход температура и конфиг на бутстрапах или флэшке.
Готовый чип с таким функционалом называется Hardware Monitor, стоит 6 баксов и стоит в любой материнской плате. Они конечно все подруливаются программно, но многие имеют небольшую энергонезависимую память внутри (точнее некоторые регистры бывают non-volatile), либо как вариант имеют куски, запитанные от VBAT, и будучи один раз настроены могут в дальнейшем работать полностью автономно. Как правило имеют в ассортименте 2-3 стратегии управления фанами и настойку зонирования.sim2q
11.04.2019 00:54Остальное там пару лапок или i2c или UART для того что бы было что закинуть в управляющий регистр. Именно этот блок мне почему то не даёт покоя как это делается полупрограммно в stm32-M0, или не разобрался. Но даже если так — то всё равно интересно самому как реальная задача.
То что вы описали, иногда гуглю в надежде что, что-то появилось. Готового и адекватного по цене — не вижу пока, всё равно тот же stm32f030p4 (~$0.3 TSSOP-20)- на вскидку там если часть каналов управления программно делать, то 5-6 каналов таких получится. 1 ADC -> 1 PWM, +UART/i2c
ps был когда то трёхногий чип в то-92 с шим выходом и сам термометром на линейку фиксированных температур с шагом в 5С и с шириной _плавной_ регулировки (а не ON/OFF которых полно) в 5С, но я даже сейчас не могу его название найти, что это было
lingvo
У вас первый модуль должен не Delay называться, а Divider — это делитель тактовой частоты для получения сигнала с периодом 1 секунда.
Не добавите в статью результаты симуляции для вашего кода, желательно помодульно — состояние входов, выходов?
ПС так и хочется продемонстрировать, что такое сегодня возможно сделать вообще без знаний Verilog/VHDL с помощью Матлаба/Симулинка в полностью графическом виде, с полной симуляцией и скорей всего быстрее. Получится намного проще и наглядней. Интересно? Сесть за статью?
mksma Автор
Согласен, divider подходит больше. Симуляцию для кода постараюсь добавить завтра, но это не точно, посмотрю как со временем завтра будет. А статью если напишите, я с удовольствием почитал бы :)
Alex_ME
Можно и в Quartus в графическом виде. Логические компоненты, проводки… Но, скорее всего, вы имели в виду другое.
mksma Автор
Добавил временную диаграмму в статью.
VioletGiraffe
А что с этим дальше делать? В железку можно залить результат?
asmolenskiy
Да. Simulink порождает исходный код.
Другое дело что в данном случае это будет операция на глазе через ж*.
Такой подход оправдан, когда Вы делаете на FPGA ЦОС — всякие фильтры и прочее такое.
Ну и глобально — владение такими средствами проектирования — это бонус, который не отменяет необходимости знания HDL языка.
lingvo
Не согласен. Сегодня Simulink дает достаточно оптимальный код и с точки зрения быстродействия и с точки зрения ресурсов. Мало того, разработчик может легко сам поменять приоритеты при генерации кода, чтобы, например, сделать полностью параллельную схему, но занимающую большую площадь, или с мультиплексированием с разделением по времени.
Попробуйте сделать то же самое на Verilog/VHDL, если вдруг окажется, что проект не пролазит по размеру или частоте — пол кода придется переписать.
Знание HDL не нужно, нужно знание принципов работы ПЛИС в основном, чтобы понимать как работают синхронные дизайны и правильно делать времянки.
asmolenskiy
Я делал это периодически с достаточно грузными IP-блоками. Причем в основном я занимался различного рода неблокирующими маршрутизаторами интерфейсов с 100% hardware offload. На такое применение данные пакеты ну совсем не кладутся.
К тому же я имел в виду кейс написания обсуждаемой прошивки.
Касательно «не проходят по частоте». По частое обычно не пролазят какие-то конкретные участки, где наворочено 100500 уровней логики и все это в том же такте заводится в какой-нить BRAM. Это все локализуемо и исправляемо. Вот прям глоабально все переписывать приходилось когда заказчик решал идеологию поменять, или добавить какую-то незначительную на его взгляд поправку в интерфейс.
Второй типовой случай не прохождения по частоте — это когда Вы сожрали все макроблоки, которые имеют фиксированные точки размещения — и синтезатор просто не может оптимально их разместить. Например съели всю память, а долбитесь в нее со всех концов ПЛИС, а она вся в 2х локациях собрана.
Нет — я не против Матлабов, Симлинков и IP-интеграторов. И даже почти не против HLS. Просто я эти вещи рассматриваю как дополнительные скиллы. Вот не владея ими можно писать что угодно. Хоть процессорные ядра. А не владея HDL (хотя бы одним) — писать что угодно — нельзя.
mksma Автор
Я с темой оптимизации не сталкивался, но возник такой вопрос. На сколько я понял Quartus тоже умеет оптимизировать код (поправьте, если не прав), оптимизирует ли Simulink код для Altera лучше, чем Quartus?
lingvo
Если использовать Simulink HDL Coder, то нет. Код будет кросс-платформенным и полностью написанным на стандартном VHDL или VERILOG. То есть его можно будет запихнуть и в altera и Xilinx и он везде скомпилируется, но при этом использование специальных фич на данном кристалле будет зависеть полностью от синтезатора — сможет ли он, например, распознать в исходном коде умножитель и запихнуть его в соответствующий Hardware блок или нет? Как правило простые вещи, как умножители, память, синтезаторы распознают неплохо и используют по мере возможности железные блоки. Но далеко не во всех случаях и проблема в том, что разработчик не всегда имеет возможность повлиять на решение синтезатора использовать логику или железный блок. Приходится придумывать уловки.
Если же использовать более специализированные тулбоксы для Simulinka (для Xilinx это System Generator, для Altera это DSP Builder), то там уже появляются оптимизированные блоки, которые хорошо ложатся на конкретную плисину и используют ресурсы, доступные только в данной модели. А в случае с умножителями или памятью дизайнеру даже предоставляется выбор из нескольких опций — использовать железный блок, логику, LUT и т.д. То есть здесь оптимизации более гибкие.
AlexanderS
Конечно да. Садитесь)