Привет, Хабр! Хочу внести свою посильную лепту в продвижение ПЛИС. В этой статье я постараюсь объяснить, как на языке VHDL описать устройство, управляющее семисегментным дисплеем. Но перед тем как начать, хочу кратко рассказать о том как я пришел к ПЛИС и почему я выбрал язык VHDL.

Где-то пол года назад решил попробывать свои силы в программировании ПЛИС. До этого со схемотехникой никогда не сталкивался. Был небольшой опыт использования микроконтроллеров (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
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 описываем входящие и исходящие сигналы устройства.

Архитектура устройства delay выглядит следующим образом
-- В секции 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 и architecture декодера
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;


Вся логика данного устройства выполняется параллельно. О том как получить формулы для данного устройства я рассказывал в одном из видео на своем канале. Кому интересно, вот ссылка на видео.

В устройстве transmitter я комбинирую последовательную и параллельную логику
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 и architecture устройства display
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.

После экземпляров компонентов объявляется процесс. Этот процесс решает несколько задач:

  1. Если передатчик не занят, то процесс инициализирует начало передачи данных
                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
                    ...
                


  2. Если передатчик занят (tr_ready_s = false), то процесс устанавливает значение сигнала disp_refresh_s <= true (этот сигнал обозначает, что по завершении передачи нужно обновить данные на дисплее). Также устанавливается значение сигнала tr_enable_s <= false, если этого не сделать до завершения передачи, то загруженные в передатчик данные будут переданы повторно
  3. Устанавливает и сбрасывает сигнал 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;
                    



Временная диаграмма


Вот как выглядит временная диаграмма передачи числа 1 на первую позицию дисплея
timing diagram

Сначала передаются данные “10011111“. Затем передается позиция числа на дисплее “00010000“ (этот параметр приходит в передатчик, как константа X”10”). В обоих случаях первым передается крайний правый бит (lsb).

Весь код можно посмотреть на github. Файлы с припиской *_tb.vhd — это отладочные файлы для соответствующих компонентов (например transmitter_tb.vhd — отладочный файл для передатчика). Их я на всякий случай тоже залил на github. Данный код был загружен и работал на реальной плате. Кому интересно, иллюстрацию работы кода можно посмотреть вот тут (начиная с 15:30). Спасибо за внимание.

Комментарии (25)


  1. lingvo
    09.04.2019 21:57
    +4

    У вас первый модуль должен не Delay называться, а Divider — это делитель тактовой частоты для получения сигнала с периодом 1 секунда.
    Не добавите в статью результаты симуляции для вашего кода, желательно помодульно — состояние входов, выходов?


    ПС так и хочется продемонстрировать, что такое сегодня возможно сделать вообще без знаний Verilog/VHDL с помощью Матлаба/Симулинка в полностью графическом виде, с полной симуляцией и скорей всего быстрее. Получится намного проще и наглядней. Интересно? Сесть за статью?


    1. mksma Автор
      09.04.2019 22:58

      Согласен, divider подходит больше. Симуляцию для кода постараюсь добавить завтра, но это не точно, посмотрю как со временем завтра будет. А статью если напишите, я с удовольствием почитал бы :)


    1. Alex_ME
      10.04.2019 00:23

      Можно и в Quartus в графическом виде. Логические компоненты, проводки… Но, скорее всего, вы имели в виду другое.


    1. mksma Автор
      10.04.2019 11:16

      Добавил временную диаграмму в статью.


    1. VioletGiraffe
      10.04.2019 11:30

      такое сегодня возможно сделать вообще без знаний Verilog/VHDL с помощью Матлаба/Симулинка в полностью графическом виде

      А что с этим дальше делать? В железку можно залить результат?


      1. asmolenskiy
        10.04.2019 11:41

        Да. Simulink порождает исходный код.
        Другое дело что в данном случае это будет операция на глазе через ж*.
        Такой подход оправдан, когда Вы делаете на FPGA ЦОС — всякие фильтры и прочее такое.
        Ну и глобально — владение такими средствами проектирования — это бонус, который не отменяет необходимости знания HDL языка.


        1. lingvo
          10.04.2019 11:51

          Не согласен. Сегодня Simulink дает достаточно оптимальный код и с точки зрения быстродействия и с точки зрения ресурсов. Мало того, разработчик может легко сам поменять приоритеты при генерации кода, чтобы, например, сделать полностью параллельную схему, но занимающую большую площадь, или с мультиплексированием с разделением по времени.
          Попробуйте сделать то же самое на Verilog/VHDL, если вдруг окажется, что проект не пролазит по размеру или частоте — пол кода придется переписать.


          Знание HDL не нужно, нужно знание принципов работы ПЛИС в основном, чтобы понимать как работают синхронные дизайны и правильно делать времянки.


          1. asmolenskiy
            10.04.2019 11:58

            Попробуйте сделать то же самое на Verilog/VHDL, если вдруг окажется, что проект не пролазит по размеру или частоте — пол кода придется переписать.

            Я делал это периодически с достаточно грузными IP-блоками. Причем в основном я занимался различного рода неблокирующими маршрутизаторами интерфейсов с 100% hardware offload. На такое применение данные пакеты ну совсем не кладутся.
            К тому же я имел в виду кейс написания обсуждаемой прошивки.
            Касательно «не проходят по частоте». По частое обычно не пролазят какие-то конкретные участки, где наворочено 100500 уровней логики и все это в том же такте заводится в какой-нить BRAM. Это все локализуемо и исправляемо. Вот прям глоабально все переписывать приходилось когда заказчик решал идеологию поменять, или добавить какую-то незначительную на его взгляд поправку в интерфейс.
            Второй типовой случай не прохождения по частоте — это когда Вы сожрали все макроблоки, которые имеют фиксированные точки размещения — и синтезатор просто не может оптимально их разместить. Например съели всю память, а долбитесь в нее со всех концов ПЛИС, а она вся в 2х локациях собрана.
            Нет — я не против Матлабов, Симлинков и IP-интеграторов. И даже почти не против HLS. Просто я эти вещи рассматриваю как дополнительные скиллы. Вот не владея ими можно писать что угодно. Хоть процессорные ядра. А не владея HDL (хотя бы одним) — писать что угодно — нельзя.


          1. mksma Автор
            10.04.2019 12:20

            Я с темой оптимизации не сталкивался, но возник такой вопрос. На сколько я понял Quartus тоже умеет оптимизировать код (поправьте, если не прав), оптимизирует ли Simulink код для Altera лучше, чем Quartus?


            1. lingvo
              10.04.2019 14:49

              Если использовать Simulink HDL Coder, то нет. Код будет кросс-платформенным и полностью написанным на стандартном VHDL или VERILOG. То есть его можно будет запихнуть и в altera и Xilinx и он везде скомпилируется, но при этом использование специальных фич на данном кристалле будет зависеть полностью от синтезатора — сможет ли он, например, распознать в исходном коде умножитель и запихнуть его в соответствующий Hardware блок или нет? Как правило простые вещи, как умножители, память, синтезаторы распознают неплохо и используют по мере возможности железные блоки. Но далеко не во всех случаях и проблема в том, что разработчик не всегда имеет возможность повлиять на решение синтезатора использовать логику или железный блок. Приходится придумывать уловки.


              Если же использовать более специализированные тулбоксы для Simulinka (для Xilinx это System Generator, для Altera это DSP Builder), то там уже появляются оптимизированные блоки, которые хорошо ложатся на конкретную плисину и используют ресурсы, доступные только в данной модели. А в случае с умножителями или памятью дизайнеру даже предоставляется выбор из нескольких опций — использовать железный блок, логику, LUT и т.д. То есть здесь оптимизации более гибкие.


    1. AlexanderS
      10.04.2019 12:29

      Интересно? Сесть за статью?

      Конечно да. Садитесь)


  1. K1804
    09.04.2019 22:57
    +1

    Как там первая реализация называлась, 155ИД1?


    1. tormozedison
      10.04.2019 00:33

      К155ИД1, ИД3, ИД10 не семисегментные, а позиционные.


  1. Alex_ME
    10.04.2019 00:29
    +1

    Я совсем дилетант в верилоге, и меня всегда интересовало слелдующее: насколько оптимально то, что генерируется по Verilog коду? Например, делитель — двоичный счетчик, последовательность триггеров. А код автора код практически как на обычных императивных языках. Что получится в итоге? Поэтому есть ряд вопросов:


    • Насколько правильно так вообще писать?
    • Какие есть правила написания эффективного verilog-кода?


    1. 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, от умения правильно вручную расположить разные блоки на «карте» чипа, распределить клоки, задействовать аппаратные возможности и т.п.


      1. AKudinov
        10.04.2019 08:27

        Не соглашусь с Вашим описанием счётчика.
        То, о чём Вы говорите (цепочка триггеров, на первый из которых подан тактовый сигнал) — это Асинхронный счётчик, или счётчик пульсаций, где каждый последующий бит тактируется предыдущим, и просто щёлкает в два раза реже. Его огромный недостаток — состоит в том, что биты на выходе встают в новое состояние последовательно, а не одновременно. Синтезировать такую конструкцию можно, видимо, только используя generate. А потом к ней ещё констрейны писАть, чтобы синтезатор знал, как тактировать триггеры…
        У автора же описан обычный синхронный счётчик, т.е. все биты clk_cnt затактированы сигналом clk. А к выходу регистра подключена асинхронная логика, вычисляющая "+1", и подающая на его же вход. А дальше — цифровой компаратор. На мой взгляд, оптимальнее было бы поставить компаратор на строгое равенство: он проще, меньше и быстрее, а значения счётчик всё равно перебирает последовательно.


        1. Andy_Big
          10.04.2019 10:32

          Да, Вы правы про триггеры, это я ступил :) Давно уже не занимался плисками…


    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
      ***
      сотни их. Мораль в том что надо читать даташиты на ПЛИСы.


    1. asmolenskiy
      10.04.2019 06:41

      Еще кстати есть такой момент как энергоэффективность.
      Чтобы ПЛИС поменьше жрала — нужно:
      1. Все что имеет входы EN — держать под EN только когда надо.
      2. Писать логику так, чтобы регистры не переключались просто так, то есть чтобы все имели какой-то «if». Так как потребление в основном обусловленно тем — каким током и как часто внутренние блочки заряжают друг другу входную емкость в момент смены своих состояний. Ну и от SLEW_RATE настроек выходных каскадов — смысл тот же, просто емкость снаружи по отношению к ПЛИС.


    1. lingvo
      10.04.2019 11:57

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


      Например bcd_to_7seg синтезатор наверняка зафигачит, как look-up-table, которую вы можете получить и с помощью LUT и используя block ram. При этом в исходном коде я бы тоже написал тупо таблицу, а не AND/OR.


  1. 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";
         ...
    


    ну и конечно, сам генератор примерно как у автора, а остальное — сервис управления и вывода прямого и комплиментарного сигналов.
    Но автор большой молодец — как-то у него все правильно и красиво, с диаграммами!


  1. sim2q
    10.04.2019 22:50

    Мечтаю сделать полностью аппаратный PWM контроллер для кулера. На вход ему кол-во оборотов + последовательный вход с тахометра, на выходе — PWM на pin для мотора.


    1. asmolenskiy
      11.04.2019 00:06
      -1

      Это не полностью аппаратный, так как кто-то же должен количество оборотов задать.
      Полностью аппаратный — это когда на вход температура и конфиг на бутстрапах или флэшке.
      Готовый чип с таким функционалом называется Hardware Monitor, стоит 6 баксов и стоит в любой материнской плате. Они конечно все подруливаются программно, но многие имеют небольшую энергонезависимую память внутри (точнее некоторые регистры бывают non-volatile), либо как вариант имеют куски, запитанные от VBAT, и будучи один раз настроены могут в дальнейшем работать полностью автономно. Как правило имеют в ассортименте 2-3 стратегии управления фанами и настойку зонирования.


      1. 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С, но я даже сейчас не могу его название найти, что это было


  1. K1804
    11.04.2019 07:23

    Интересно было бы сравнить с дешифратором на PROM, например на 155ре3. По сложности получившейся схемы и току потребления.