assign تصف حقيقة دائمة
إعلان wire يُنشئ إشارة. assign تصف ما يقودها. العلاقة مستمرة: مهما كان على الطرف الأيمن، فإن الطرف الأيسر يساويه في كل لحظة زمنية مُحاكاة:
wire y;
assign y = a & b;
شيئان يستتبعهما هذان السطران:
yموجود كـ wire في الدارة.yيساوي طوال الوقت AND على مستوى البتات بينaوb. غيّر أيًا من المدخلين فيتبعهy.
لا ساعة ولا حدث يُفعّل التحديث. يرى المحاكي تغيّر a أو b، فيُعلّم y كقذِر ويعيد تقييم التعبير. في العتاد، هذا يُترجم إلى زوج من بوابات AND: توافقي، بلا حالة، فوري.
الشكل الضمني
يمكنك جمع الإعلان والإسناد في سطر واحد:
wire y = a & b;
هذا هو نفسه السطران أعلاه. مفيد للأسلاك المضمَّنة التي لا تهتم بها أشياء خارج هذا النطاق. كثير من دلائل الأسلوب تفضّل الشكل الضمني فعلًا لأنه يضع الإعلان بجوار المعادلة.
ما يمكن لـ assign قيادته
هدف assign يجب أن يكون من نوع net - في Verilog العادي هذا wire (أو أحد أبناء العم النادرين مثل tri، wand، wor). لا يمكنه أن يكون reg. إن أعلنت هدفك بالخطأ كـ reg:
reg y;
assign y = a & b; // ERROR: cannot drive reg with assign
سيخبرك المترجم. إما غيّر الهدف إلى wire، أو انقل المنطق إلى كتلة always @(*) حيث reg هو الهدف الشرعي.
الطرف الأيمن يمكن أن يكون أي شيء يُقَيَّم إلى قيمة: حروف، إشارات، parameters، تعابير عوامل، استدعاءات دوال. يمكنه مزج عروض دخل متعددة؛ تُطبَّق قواعد التوسيع المعتادة.
متى تستخدم assign مقابل always
كلاهما يمكن أن يُنتج منطقًا توافقيًا. الاختيار في الغالب بشأن كيف تقرأ الشيفرة:
assignهي الأفضل عندما تكون العلاقة تعبيرًا واحدًا. الجامعات، muxes البسيطة المبنية بـ?:، الأقنعة، بتات parity، أي شيء يمكنك كتابته في سطر واحد.always @(*)هي الأفضل عندما تحتاج جملًا إجرائية. جملcaseمتعددة الفروع،if/else ifمتداخلة، أي شيء يستفيد منregs وسيطة مُسماة. نغطّي هذا في كتلة Always.
إليك نفس mux 4-إلى-1 مكتوبًا بالطريقتين:
كلا module يُركّبان إلى نفس multiplexer أساسًا. نسخة assign سطر واحد من الشيفرة؛ نسخة always ستة أسطر. لأربع حالات الفرق ضيق؛ لستة عشر حالة، كتلة case أوضح للقراءة بكثير.
أنماط شائعة
منطق توافقي عادي
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
تعبير واحد، wire واحد. الخبز والزبدة لـ assign.
mux 2-إلى-1
assign out = sel ? a : b;
تعبير شرطي واحد - تُحوّله أداة التركيب إلى mux واحد 2-إلى-1. أنظف كتابة ممكنة لـ "اختر بين a وb".
تجميع البتات
assign status = {error, overflow, ready, busy, 4'b0};
التسلسل (concatenation) على الطرف الأيمن لـ assign هو كيف تجمع الرايات في بايت حالة. تُحسب النتيجة وتُقاد باستمرار.
خرج tri-state
assign data_pin = output_enable ? data_out : 1'bz;
عندما يكون output_enable مرتفعًا، قُد الـ pin. وعندما يكون منخفضًا، حرّره إلى المعاوقة العالية. هذا النمط المعتاد عند pins الشريحة حيث قد تتشارك عدة قائدات سلكًا.
التزامن: عدة assigns ليست تسلسلية
تذكير لن يكفّ عن الأهمية: عدة جمل assign في نفس module كلها تعمل بالتوازي. ليست تسلسلًا:
assign y = a & b; // موجودة طوال الوقت
assign z = a | b; // موجودة أيضًا طوال الوقت، باستقلال
الترتيب في الملف غير مهم. كلتا المعادلتين صحيحتان في وقت واحد. قد تضع أداة التركيب بوابة AND قبل بوابة OR أو بعدها؛ لا يهم، كلتا البوابتين تعملان باستمرار.
إن أردت سلوكًا يبدو تسلسليًا، فأنت تتجه إلى كتلة always (وعلى الأرجح ساعة). هذا فصل آخر.
قائدون متعددون: نمط الـ bus
يمكن أن يكون لـ wire أكثر من assign يستهدفه، لكنك تقريبًا لن تريد ذلك إلا لـ tri-state buses. قائدان يتصارعان على سلك يُنتج سلوكًا غير معرَّف:
assign y = a;
assign y = b; // BAD - two drivers, simulator picks one or x's it out
النمط الشرعي: كل قائد يُحرّر إلى z عند الخمول، وفي أي وقت يكون قائد واحد على الأكثر نشطًا.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
ذلك يعمل لأن في أي وقت معطى، يُنتج على الأكثر واحد من الـ ternaries قيمة غير z. قيمة السلك الفعلية هي ما يقوده القائد غير المُحرَّر.
في المنطق الداخلي - في أي مكان ليس pin شريحة أو bus مشترك على الشريحة - قائد واحد لكل سلك. أخطاء القائد المتعدد مزعجة في التنقيح.
ما لا يمكن لـ assign فعله
بعض الأشياء التي assign ليست الأداة الصحيحة لها:
- التخزين.
assignتصف علاقات توافقية؛ لا يمكنها إدخال flip-flop. إن احتجت قيمة تُحفَظ عبر دورات الساعة، فهذا عمل كتلةalways @(posedge clk). - منطق إجرائي متعدد الخطوات. لا يمكنك كتابة
if/elseأوcaseداخلassign. أقرب ما تحصل عليه هو سلسلة?:، التي تصبح قبيحة بعد ثلاثة فروع. - قيادة registers من داخل كتلة إجرائية. أهداف
regتحتاج إسنادًا إجرائيًا، لاassign.
معرفة الحدود هي كيف تقرر متى تتحوّل إلى always.
ماذا بعد
شاهدت الآن الجانب الهيكلي الكامل لـ Verilog: إعلان modules، وأخذ نسخ منها، وتوصيل المنطق التوافقي بـ assign. الفصل التالي ينتقل إلى الكتل الإجرائية - بناءات initial وalways حيث يبدأ الزمن والترتيب في الأهمية.
الأسئلة الشائعة
ما هي continuous assignment في Verilog؟
assign target = expression; تُعلن عن علاقة دائمة مستمرة: target يساوي expression دائمًا. كلما تغيّرت أي إشارة في التعبير، يُعيد المحاكي تقييم الطرف الأيمن ويُحدّث target. لا ساعة ولا حدث - العلاقة صحيحة في كل لحظة زمنية.
ماذا يمكنك استهدافه بـ assign في Verilog؟
assign يمكن أن تقود wire، لكن أبدًا reg. الهدف يجب أن يكون من نوع net. إن كنت تريد الإسناد إلى شيء داخل كتلة always بدلًا من ذلك، فأعلِنه reg. سيرفض المترجم assign x = ... إن كان x من نوع reg، ويرفض x = ... داخل always إن كان x من نوع wire.
متى أستخدم assign مقابل كتلة always؟
استخدم assign لمنطق توافقي بسيط - تعبير واحد بدخل واحد، إشارة خرج واحدة، بلا حاجة لـ if/else. استخدم always @(*) عندما يحتاج المنطق إلى جمل إجرائية (case، سلسلة if/else if، حلقة for). كلاهما يُنتج عتادًا توافقيًا؛ الاختيار يخصّ القابلية للقراءة.
هل يمكن وجود عدة assigns على نفس السلك في Verilog؟
فقط إذا كنت تنمذج tri-state bus حيث يُحرّر كل قائد السلك إلى z عند الخمول. جملتا assign تحاولان قيادة السلك إلى قيم محدّدة في الوقت نفسه تُنتجان تنافسًا - قد يختار المحاكي إحداها، أو يجعل الإشارة X، حسب الأداة. للمنطق التوافقي العادي، قائد واحد لكل سلك.