مرة أخرى حول التأخير في شفرة المصدر لمشروع FPGA أو سؤال بسيط لمقابلة لوظيفة مطور FPGA



منذ بعض الوقت ، أثناء مناقشة في شركة مطوري FPGA المحترفين ، نشأ نقاش حول اجتياز مقابلة. ما هي الأسئلة المطروحة هناك ، وما الذي يمكن طرحه. اقترحت سؤالين:

  1. أعط مثالاً لرمز متزامن دون استخدام التأخيرات ، مما سيعطي نتائج مختلفة عند النمذجة وعند العمل في معدات حقيقية
  2. تصحيح هذا الرمز مع التأخير.

بعد هذا السؤال ، جرت مناقشة حية ، ونتيجة لذلك قررت النظر في هذه المسألة بمزيد من التفصيل.

لقد تطرقت بالفعل إلى هذه المشكلة قليلاً في المقالة السابقة . الآن بمزيد من التفصيل. هنا نص مثال:

library IEEE; use IEEE.STD_LOGIC_1164.all; entity delta_delay is end delta_delay; architecture delta_delay of delta_delay is signal clk1 : std_logic:='0'; signal clk2 : std_logic; alias clk3 : std_logic is clk1; --    clk1 signal a : std_logic; signal b : std_logic; signal c : std_logic; signal d : std_logic; begin ---    --- clk1 <= not clk1 after 5 ns; pr_a: process begin a <= '0' after 1 ns; wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); a <= '1' after 1 ns; wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); end process; ---   -    --- clk2 <= clk1; --    ,        ---  1 -     --- b <= a when rising_edge( clk1 ); c <= b when rising_edge( clk1 ); d <= b when rising_edge( clk2 ); ---  2 -     --- -- --clk2 <= clk1; --b <= a after 1 ns when rising_edge( clk1 ); --c <= b after 1 ns when rising_edge( clk1 ); --d <= b after 1 ns when rising_edge( clk2 ); ---  3 -          alias --- --b <= a when rising_edge( clk1 ); --c <= b when rising_edge( clk1 ); --d <= b when rising_edge( clk3 ); end delta_delay; 

من أجل البساطة ، يتم وضع جميع التعليمات البرمجية في مكون واحد.

الإشارات clk1 و a هي إشارات التعرض للاختبار. clk1 هو تردد ساعة 100 ميجاهرتز ، تحمل الإشارة a دورتين للساعة عند 0 وأربع دورات للساعة عند 1. يتم إنشاء الإشارة a بتأخير 1 nc بالنسبة إلى الحافة المرتفعة من clk1 . هاتان الإشارتان تكفيان لوصف المشكلة.

يمكن أن تكون خيارات الرموز المركبة المركبة مختلفة ونمذجة.
ضع في اعتبارك الخيار الأول ، هذا هو رمز توليف دون تأخير واستخدام إعادة تعيين تردد الساعة.

فيما يلي نتائج المحاكاة للخيار 1:



يوضح الرسم البياني بصريًا أن إشارات الساعة clk1 و clk2 تتزامن ، ولكن في الواقع تتأخر clk2 بالنسبة إلى clk1 بقيمة تأخير دلتا. تتأخر الإشارة c في الإشارة b بواسطة دورة ساعة واحدة. هذا صحيح. ولكن يجب أن تتزامن الإشارة d مع الإشارة c ، لكن هذا لا يحدث. يعمل في وقت سابق.

دعونا نتذكر ما هو تأخير دلتا. هذا مفهوم أساسي ، وهو يعتمد على عمل محاكيات الأحداث ، التي نستخدمها عند نمذجة الدوائر المنطقية.

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

 clk1 <= not clk1 after 5 ns; 

لنفترض الآن أننا نمثل clk1 فقط ، ولا توجد إشارات أخرى.
في اللحظة الأولى من الوقت ، clk1 يساوي 0 ، يتم تعيين هذا عندما يتم الإعلان عن الإشارة. يرى جهاز محاكاة شرط لعكس الإشارة. تعطي الكلمة الأساسية بعد تعليمات لتعيين قيمة جديدة في 5 نانوثانية بالنسبة إلى وقت النموذج الحالي. يرى المحاكي هذا ويلاحظ أنه في الوقت 5 نانوثانية ستكون قيمة clk1 1. في حين أن هذا هو مستقبل النموذج ، بالمناسبة قد لا يزال يتغير. بعد ذلك ، يقوم المحاكي بمسح الإشارات المتبقية. سيرى المحاكي أنه في لحظة معينة في وقت النموذج ، يتم كل شيء ويمكنه حساب اللحظة التالية. السؤال الذي يطرح نفسه - ما هي اللحظة التالية؟ من حيث المبدأ ، هناك خيارات مختلفة ممكنة. على سبيل المثال ، يحتوي Simulink على وضع خطوة ثابتة. في هذه الحالة ، سيزداد وقت النموذج بمقدار معين وستستمر العمليات الحسابية.

تعمل أنظمة محاكاة الدوائر الرقمية بشكل مختلف. ينتقلون إلى الحدث التالي ، الذي وضعوه بالفعل في المستقبل على محور وقتهم النموذجي. في هذه الحالة ، سيكون 5 نانوثانية. سيشاهد جهاز المحاكاة أن clk1 قد تغير وسيحسب قيمة جديدة لذلك ، سيكون 0 والذي سيتم وضعه أيضًا بتأخير 5 ns على محور الوقت. على سبيل المثال سيكون 10 نانوثانية. وهكذا ستستمر العملية حتى ينتهي وقت المحاكاة المحدد.

الآن دعنا نضيف الإشارات أ و ب .

يتم تعيين الإشارة أ في العملية. للإشارة ب ، البناء الشرطي عند استخدامه ؛ تحلّل الدالة height_edge ( clk1 ) clk1 وتعود صحيحة عند إصلاح الواجهة ، أي القيمة السابقة هي 0 والقيمة الحالية هي 1.

في وقت النموذج 5 نانوثانية ، سوف يتغير clk1 . ستصبح مساوية لـ 1 وفي لحظة 10 نانوثانية سيتم إنشاء حدث لضبطه على 0. ولكن هذا لاحقًا. بينما نحن لا نزال في لحظة نانوثانية ونستمر في الحسابات. يذهب المحاكي إلى الخط
 b<=a when rising_edge(clk1); 
نظرًا لوجود دالة تعتمد على clk1 ، فإن المحاكي سيحسب قيمة الوظيفة ، ويرى أنها ترجع صحيحة وستخصص
 b<=a; 


هنا يبدأ الجزء الأكثر إثارة للاهتمام - عندما يكون من الضروري تغيير قيمة b . يبدو من الضروري تغييره الآن ، في هذه المرحلة من الزمن. لكن لدينا عمليات موازية. ربما ما زلنا بحاجة إلى قيمة b لحساب الإشارات الأخرى. وهنا يأتي مفهوم تأخير الدلتا. هذه هي القيمة الدنيا التي يتغير بها وقت النموذج. هذه القيمة ليس لها بعد الوقت. هذه مجرد دلتا. ولكن يمكن أن يكون هناك الكثير منهم. وإلى حد أن جهاز المحاكاة يتوقف ببساطة عن طريق الخطأ أو يتجمد.
لذا ، سيتم تعيين قيمة جديدة لـ b للحظة 5 ns + 1 (1 هو أول تأخير دلتا). سيشاهد جهاز المحاكاة أنه لا يوجد شيء بالفعل للحساب في الوقت الحالي 5 نانوثانية وسيذهب إلى اللحظة التالية ، وسيكون هذا 5 نانوثانية + 1 ؛ في هذه اللحظة ، لا يعمل ارتفاع (ckl1). وسيتم تعيين قيمة b إلى 1. بعد ذلك ، ستنتقل المحاكاة إلى اللحظة 10 nc.

الآن دعنا نضيف الإشارات c و d ونرى سبب اختلافها.
من الأفضل التفكير في لحظة الوقت النموذجي 25 ns مع مراعاة تأخيرات دلتا

دلتاclk1clk2re (clk1)re (clk2)بجد
010صحيحخطأ000
111خطأصحيح100
210خطأخطأ101

ملحوظة: إعادة ارتفاع

يوضح الجدول أنه في اللحظة التي يتم فيها تشغيل وظيفة height_edge ( clk2 ) ، تكون قيمة b هي بالفعل 1. وبالتالي ، سيتم تعيينها للإشارة d .

بناءً على الحس السليم ، ليس هذا هو السلوك الذي توقعناه من الشفرة. بعد كل شيء ، قمنا ببساطة بإعادة تعيين إشارة clk1 إلى clk2 وتوقعنا أن تكون الإشارات c و d هي نفسها. ولكن باتباع منطق المحاكاة ، فإن الأمر ليس كذلك. هذه ميزة PRINCIPAL . هذه الميزة ، بالطبع ، يجب أن تكون معروفة لمطوري مشاريع FPGA وبالتالي هذا سؤال جيد وضروري للمقابلة.

ماذا سيحدث أثناء التوليف؟ لكن المزج سيتبع الحس السليم ، سيجعل الإشارات clk2 و clk1 إشارة واحدة وبالتالي فإن c و d سيكونان متماثلين أيضًا. ومع بعض إعدادات المزج ، سيتم دمجها أيضًا في إشارة واحدة.

هذا هو الحال عندما تؤدي النمذجة والعمل في معدات حقيقية إلى نتائج مختلفة. أريد أن أشير إلى أن سبب النتائج المختلفة هو المنطق المختلف لجهاز المحاكاة والمزج. هذا اختلاف أساسي. هذا لا علاقة له بقيود الوقت. وإذا أظهر مشروعك في النموذج وفي الحديد نتائج مختلفة ، فقم بالتحقق ، ربما يكون مثل هذا التصميم قد تسلل إلى هناك

 clk2 <= clk1 

الآن السؤال الثاني هو إصلاح هذا الرمز مع التأخير.
هذا هو الخيار 2. يمكن أن يكون غير مصاغ ونمذجة.
ها هي النتيجة.



والنتيجة صحيحة. ماذا حدث؟ لنقم بعمل جدول مرة أخرى لفاصل زمني 25-36 نانوثانية
الوقتدلتاclk1clk2re (clk1)re (clk2)بجد
25010صحيحخطأ000
25111خطأصحيح000
26011خطأخطأ100
35010صحيحخطأ100
35111خطأصحيح100
36011خطأخطأ111

يمكن ملاحظة أن قيمة b لا تتغير في لحظات الجبهات clk1 ، clk2 . يستغرق التأخير لمدة 1 ns لحظة تغير الإشارة خارج منطقة استجابة الحافة. هذا الرمز يقترب من الواقع. في دائرة حقيقية ، هناك بعض الوقت حتى يتم تشغيل الزناد ولكي تنتشر الإشارة. يجب أن يكون هذا الوقت أقل من فترة تكرار الساعة ، في الواقع ، هذا ما يفعله التتبع ، وهذا ما يتحقق منه تحليل الوقت.

سبب الخطأ هو إعادة تعيين إشارة الساعة عن طريق التخصيص المعتاد الذي يظهر عنده تأخير دلتا. ومع ذلك ، فإن لغة VHDL لها اسم مستعار. هذا يسمح لك بالحصول على اسم مختلف للإشارة. هذا هو الإعلان:

 alias clk3 : std_logic is clk1; 

في نص المثال ، يمكنك إلغاء تعليق الخيار 3 - سيعمل بشكل صحيح.

هذا المثال مكتوب بلغة VHDL. ربما هذه هي مشكلة هذه اللغة فقط؟ ولكن هنا نفس الخيارات في Verilog.

نص مخفي
 `timescale 1 ns / 1 ps module delta_delay_2 (); reg clk1 = 1'b0; reg clk2; wire clk3; reg a = 1'b0; reg b; reg c; reg d; initial begin forever clk1 = #5 ~clk1; end initial begin repeat(10) begin #20 a = 1'b1; #60 a = 1'b0; end end //   -    --- always @(clk1) clk2 <= clk1; //  1 -     always @(posedge clk2) d <= b; always @(posedge clk1) begin c <= b; b <= a; end //  2 -     //always @(posedge clk1) b = #1 a; // //always @(posedge clk1) c = #1 b; // //always @(posedge clk2) d = #1 b; //  3 -     //      assign //assign clk3 = clk1; // //always @(posedge clk3) d <= b; // //always @(posedge clk1) //begin // c <= b; // b <= a; //end endmodule 



  • الخيار 1 - بدون تأخير. لا يعمل بشكل صحيح.
  • الخيار 2 - مع التأخير. يعمل بشكل صحيح.
  • الخيار 3 - إعادة التعيين عن طريق الأسلاك. يعمل بشكل صحيح.

Verilog لديه مفهوم ريج والأسلاك. في هذه الحالة ، تبدو إعادة تعيين إشارة الساعة عبر الأسلاك أكثر طبيعية. هذا مماثل لتعيين اسم مستعار في VHDL. هذا يخفف إلى حد ما من توتر المشكلة ، لكنك لا تزال بحاجة إلى معرفة ذلك.
لدى Verilog أيضًا مفهوم الحظر والمهمة غير المحظورة. يمكن كتابة تخصيص الإشارة b و c بطريقة أخرى:

 always @(posedge clk1) begin c = b; b = a; end 

ويمكنك القيام بذلك:

 always @(posedge clk1) begin b = a; c = b; end 

اعتمادًا على ترتيب الخطوط ، ستكون النتيجة مختلفة.

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

تتوفر ملفات عينة هنا.

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


All Articles