مرحبا يا هبر! أريد أن أسهم في النهوض ب 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 ، يتم استخدام بناء مكونين من الكيان والهندسة المعمارية. الكيان يعلن واجهة للعمل مع الجهاز. يصف العمارة منطق الجهاز.
إليك ما يبدو عليه كيان جهاز التأخير من خلال الحقل العام ، يمكننا ضبط الجهاز على التأخير المطلوب. وفي مجال المنافذ نصف الإشارات الواردة والصادرة للجهاز.
هيكل جهاز التأخير على النحو التالي يتم تنفيذ الكود الموجود داخل قسم العملية بالتتابع ، ويتم تنفيذ أي كود آخر بالتوازي. بين قوسين ، بعد الكلمة الأساسية للعملية ، تتم الإشارة إلى الإشارات عن طريق تغيير العملية التي ستبدأ (قائمة الحساسية).
جهاز 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
بالنسبة إلى إشارة 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";
يتحكم هذا الجهاز في الأجهزة الأخرى. هنا ، قبل إعلان الإشارات المساعدة ، أعلن عن المكونات التي سأستخدمها. في البنية نفسها (بعد الكلمة الرئيسية لبدء) أقوم بإنشاء مثيلات الجهاز:
- 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.
بعد مثيلات المكون ، يتم الإعلان عن العملية. هذه العملية تحل العديد من المشاكل:
إذا لم يكن جهاز الإرسال مشغولًا ، فستقوم العملية بتهيئة بداية نقل البيانات. if(tr_ready_s) then if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s;
- إذا كان المرسل مشغولاً (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;
توقيت الرسم البياني
فيما يلي مخطط توقيت نقل الرقم 1 إلى الموضع الأول للعرض أولاً ، يتم نقل البيانات "10011111". ثم يكون موضع الرقم على الشاشة هو "00010000" (تصل هذه المعلمة إلى جهاز الإرسال كـ X ثابتة 10 "). في كلتا الحالتين ، يتم إرسال بت أقصى اليمين (lsb) أولاً.
كل رمز يمكن الاطلاع على
جيثب . الملفات التي تحتوي على منخفض * _tb.vhd هي ملفات تصحيح للمكونات المقابلة (على سبيل المثال ، transmitter_tb.vhd هو ملف تصحيح لمرسل). فقط في حالة ، أنا أيضا تحميلها على جيثب. تم تنزيل هذا الرمز وعمله على لوحة حقيقية. من يهتم ، يمكنك رؤية توضيح للرمز
هنا (يبدأ في الساعة 15:30). شكرا لاهتمامكم