FPGA سبعة قطعة عرض التحكم

مرحبا يا هبر! أريد أن أسهم في النهوض ب FPGAs. سأحاول في هذه المقالة شرح كيف أصف في VHDL جهازًا يتحكم في عرض مكون من سبعة أجزاء. لكن قبل البدء ، أريد أن أتحدث بإيجاز عن كيف أتيت إلى FPGA ولماذا اخترت لغة VHDL.

منذ حوالي نصف عام قررت تجربة يدي في برمجة FPGAs. قبل ذلك ، لم أصادف دوائر. كانت هناك خبرة قليلة في استخدام ميكروكنترولر (Atmega328p ، STM32). مباشرة بعد قرار الشعور بالراحة مع FPGAs ، نشأ السؤال عن اختيار اللغة التي سأستخدمها. وقع الاختيار على VHDL بسبب كتابته الصارمة. كمبتدئ ، أردت أن ألتقط أكبر قدر ممكن من المشاكل في مرحلة التوليف ، وليس على جهاز يعمل.

لماذا بالضبط عرض سبعة قطعة؟ وميض LED متعب بالفعل ، ومنطق وميضه لا يمثل أي شيء مثير للاهتمام. منطق التحكم في الشاشة من ناحية أكثر تعقيدًا من وميض مؤشر LED (بمعنى أن كتابته أكثر إثارة للاهتمام) ، ومن ناحية أخرى ، فإنه من السهل جدًا تنفيذه.

ما استخدمته في عملية إنشاء الجهاز:

  • FPGA Altera Cyclone II (أعرف أنه عفا عليه الزمن بشكل ميؤوس منه ، لكن الصينيين يمكنهم شراؤه مقابل بنس واحد)
  • Quartus II الإصدار 13.0.0 (على حد علمي ، هذا هو أحدث إصدار يدعم الإعصار II)
  • محاكي ModelSim
  • سبعة الجزء العرض مع سجل التحول

مهمة


قم بإنشاء جهاز يعرض الأرقام من 0 إلى 9. في دورة ما ، مرة واحدة في الثانية ، يجب أن تزيد القيمة المعروضة على الشاشة بمقدار 1.

هناك العديد من الطرق لتنفيذ هذا المنطق. سأقسم هذا الجهاز إلى وحدات ، كل منها سيؤدي بعض الإجراءات وستنتقل نتيجة هذا الإجراء إلى الوحدة التالية.

وحدات


  • يجب أن يكون هذا الجهاز قادرًا على حساب الوقت. لحساب الوقت ، قمت بإنشاء وحدة "تأخير". تحتوي هذه الوحدة على إشارة واردة واردة واحدة. تستقبل الوحدة إشارة تردد FPGA ، وبعد عدد محدد من الفترات للإشارة الواردة ، تغير قيمة الإشارة الصادرة إلى الاتجاه المعاكس.
  • يجب أن يقرأ الجهاز من 0 إلى 9. سيتم استخدام وحدة bcd_counter لهذا الغرض.
  • من أجل إضاءة جزء على الشاشة ، تحتاج إلى ضبط البت المطابق للقطعة على 0 في سجل الإزاحة للشاشة ، ولكي تمسح القطعة إلى وحدات بت ، اكتب 1 (لقد قلبت شاشتي المنطق). سوف تتعامل وحدة فك ترميز bcd_2_7seg مع تثبيت وإعادة تعيين البتات المطلوبة.
  • وحدة الارسال ستكون مسؤولة عن نقل البيانات.

سيتحكم الجهاز المضيف في الإرسال الصحيح للإشارات بين الوحدات ، وكذلك يولد إشارة rclk عند الانتهاء من نقل البيانات.

من أجل الوضوح ، أقدم مخططًا لهذا الجهاز
مخطط

كما ترون من الرسم التخطيطي ، يحتوي الجهاز على إشارة واردة واحدة (clk) و 3 إشارات صادرة (sclk ، dio ، rclk). تأتي إشارة clk في فواصل إشارة (sec_delay و transfer_delay). إشارة صادرة مع فترة من 1s يترك الجهاز sec_delay. على الحافة الصاعدة لهذه الإشارة ، يبدأ العداد (bcd_counter1) في إنشاء الرقم التالي للعرض. بعد إنشاء الرقم ، تقوم وحدة فك الترميز (bcd_2_7seg1) بتحويل التمثيل الثنائي للرقم إلى مقاطع مضاءة وليس مضاءة على الشاشة. والتي ، باستخدام جهاز الإرسال (transmitter1) ، يتم إرسالها إلى الشاشة. الارسال هو توقيت باستخدام جهاز transfer_delay.

قانون


لإنشاء جهاز في VHDL ، يتم استخدام بناء مكونين من الكيان والهندسة المعمارية. الكيان يعلن واجهة للعمل مع الجهاز. يصف العمارة منطق الجهاز.

إليك ما يبدو عليه كيان جهاز التأخير
entity delay is --   entity,  generic    generic (delay_cnt: integer); --       port(clk: in std_logic; out_s: out std_logic := '0'); end entity 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; 


يتم تنفيذ الكود الموجود داخل قسم العملية بالتتابع ، ويتم تنفيذ أي كود آخر بالتوازي. بين قوسين ، بعد الكلمة الأساسية للعملية ، تتم الإشارة إلى الإشارات عن طريق تغيير العملية التي ستبدأ (قائمة الحساسية).

جهاز bcd_counter ، من حيث منطق التنفيذ ، مطابق لجهاز التأخير. لذلك ، لن أتناولها بالتفصيل.

هنا هو الكيان والهندسة المعمارية لجهاز فك التشفير
 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 التي تدخل جهاز الإرسال ، ولكن فقط إذا كان الجهاز يرسل حاليًا البيانات (إشارة جاهزة = false). خلاف ذلك ، ستكون قيمة إشارة sclk هي 0. في بداية نقل البيانات (تمكين = إشارة حقيقية) ، أقوم بدمج البيانات من متجهين من 8 بت (digit_pos و digit) يدخلان الجهاز في متجه 16 بت (data_v) وأرسله البيانات من هذا المتجه واحدة بت في الساعة ، وتعيين قيمة البتة المرسلة في ديو إشارة المنتهية ولايته. من بين الأشياء المثيرة للاهتمام حول هذا الجهاز ، أريد أن أشير إلى أن البيانات الموجودة في dio قد تم ضبطها على الحافة الخلفية لإشارة clk ، وسيتم كتابة البيانات من pin dio إلى سجل الإزاحة الخاص بالشاشة عند وصول الحافة الأمامية لإشارة sclk. عند الانتهاء من عملية النقل ، من خلال إعداد <= إشارة حقيقية جاهزة ، أشير إلى أجهزة أخرى أكملها الإرسال.

إليك ما يبدو عليه كيان وهيكل جهاز العرض
 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; 


يتحكم هذا الجهاز في الأجهزة الأخرى. هنا ، قبل إعلان الإشارات المساعدة ، أعلن عن المكونات التي سأستخدمها. في البنية نفسها (بعد الكلمة الرئيسية لبدء) أقوم بإنشاء مثيلات الجهاز:

  • sec_delay - مثيل مكون التأخير. يتم توجيه الإشارة الصادرة إلى ثوانٍ.
  • transfer_delay - مثيل مكون التأخير. يتم إرسال الإشارة الصادرة إلى إشارة transfer_clk.
  • bcd_counter1 - مثيل لمكون bcd_counter. يتم توجيه الإشارة الصادرة إلى bcd_counter_s.
  • bcd_to_7seg1 - مثيل لمكون bcd_to_7seg. يتم توجيه الإشارة الصادرة إلى disp_out_s.
  • الارسال 1 هو مثيل لمكون الارسال. يتم إرسال الإشارات الصادرة إلى إشارات 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 إلى الموضع الأول للعرض
مخطط التوقيت

أولاً ، يتم نقل البيانات "10011111". ثم يكون موضع الرقم على الشاشة هو "00010000" (تصل هذه المعلمة إلى جهاز الإرسال كـ X ثابتة 10 "). في كلتا الحالتين ، يتم إرسال بت أقصى اليمين (lsb) أولاً.

كل رمز يمكن الاطلاع على جيثب . الملفات التي تحتوي على منخفض * _tb.vhd هي ملفات تصحيح للمكونات المقابلة (على سبيل المثال ، transmitter_tb.vhd هو ملف تصحيح لمرسل). فقط في حالة ، أنا أيضا تحميلها على جيثب. تم تنزيل هذا الرمز وعمله على لوحة حقيقية. من يهتم ، يمكنك رؤية توضيح للرمز هنا (يبدأ في الساعة 15:30). شكرا لاهتمامكم

Source: https://habr.com/ru/post/ar447454/


All Articles