جعل رأس محلل الناقل USB على أساس مجمع Redd

في المادتين الأخيرتين ، اعتبرنا مثالاً على "البرامج الثابتة" لمجمع Redd ، مما جعل جزء FPGA الخاص به عبارة عن محلل منطقي للأغراض العامة. ثم رغبت في اتخاذ الخطوة التالية وتحويلها إلى محلل ناقل USB. والحقيقة هي أن أجهزة التحليل ذات العلامات التجارية من هذا النوع مكلفة للغاية ، وأحتاج إلى التحقق من سبب عمل نفس USB ، إذا تم توصيله بالجهاز ، وإذا قمت بتشغيل الجهاز عندما يكون كل شيء متصلاً بالفعل بالموصل ، فإنه لا يعمل. أي أن محللي البرامج لا يمكنهم التأقلم هنا. بينما كنت أكتب ، بطريقة ما ذهبت بعيدا وكتبت كتلة من خمس مقالات. يمكننا الآن أن نقول أنهم لا يظهرون فقط المحلل نفسه ، ولكن أيضًا العملية النموذجية لإنشائه في وضع "التسرع". ستوضح لك المقالة كيفية صنع مثل هذا المحلل ليس فقط على أساس Redd ، ولكن أيضًا على ألواح الخبز الجاهزة ،والتي يمكن شراؤها من علي إكسبريس.









ربما ، اليوم سأقوم حتى بكسر التقليد وسأقوم بتصحيح المشروع ليس على مجمع Redd ، ولكن على تخطيط منتظم. أولاً ، أدرك أن الأغلبية الساحقة من القراء لا يمكنهم الوصول إلى مثل هذا المجمع ، لكنهم يستطيعون الوصول إلى Ali Express. حسنًا ، وثانيًا ، أنا كسول جدًا بحيث لا أسيطر على حديقة مع زوج من جهاز USB ومضيف متصل ، وأيضًا للتعامل مع التدخل الناشئ.



في عام 2017 ، كنت أبحث عن حلول جاهزة على الشبكة ووجدت شيئًا رائعًا ، أو بالأحرى ، سلفه. الآن لديهم كل شيء على لوحة متخصصة ، ولكن في كل مكان كانت هناك صور فوتوغرافية للوحة توصيل بسيطة من Xilinx ، والتي تم توصيل لوحة من WaveShare (يمكنك التعرف عليها هنا ). دعونا نلقي نظرة على صورة هذا المنتدى.







يحتوي على منفذي USB في آن واحد. علاوة على ذلك ، يوضح الرسم البياني أنها متوازية. يمكنك توصيل أجهزة USB الخاصة بك بمقبس من النوع A ، ويمكنك توصيل كابل بموصل USB صغير ، والذي سنقوم بتوصيله بالمضيف. ويوضح وصف مشروع OpenVizsla أن هذه الطريقة تعمل. المؤسف الوحيد هو أن المشروع نفسه يصعب قراءته. يمكنك أن تأخذه على github ، لكني سأعطي رابطًا ليس للحساب المشار إليه في الصفحة ، وسيجده الجميع على أي حال ، ولكن تم إعادة تصميمه لـ MiGen ، ولكن الإصدار الذي وجدته في 2017: http: // github. كوم / فائقة النواة / النوى، إنه على Verilog نظيف ، وهناك فرع usb_sniffer. هناك ، لا يمر كل شيء مباشرة عبر ULPI ، ولكن من خلال محول ULPI إلى UTMI (كلتا هاتين الكلمات الفاحشة عبارة عن دوائر دقيقة ذات مستوى مادي تتطابق مع قناة USB 2.0 عالية السرعة مع حافلات مفهومة للمعالجات و FPGAs) ، وبعد ذلك فقط تعمل مع UTMI. كيف يعمل كل شيء هناك ، لم أحسب. لذلك ، فضلت أن أجعل تطوري من الصفر ، حيث سنرى قريبًا أن كل شيء مخيف هناك وليس صعبًا.



ما هي الأجهزة التي يمكنك العمل عليها



الإجابة على السؤال من العنوان بسيطة: على أي شخص لديه FPGA وذاكرة خارجية. بالطبع ، في هذه السلسلة ، سننظر فقط في Altera FPGAs (Intel). ومع ذلك ، ضع في اعتبارك أن البيانات من ULPI microcircuit (الموجودة على هذا المنديل) تعمل عند 60 ميجاهرتز. الأسلاك الطويلة غير مقبولة هنا. من المهم أيضًا توصيل خط CLK بإدخال FPGA من مجموعة GCK ، وإلا فسيعمل كل شيء ثم يفشل. من الأفضل عدم المخاطرة به. لا أنصحك بإعادة توجيهه برمجيًا. حاولت. انتهى كل شيء بسلك في الساق من مجموعة GCK.



لتجارب اليوم ، بناء على طلبي ، ملحوم لي أحد المعارف مثل هذا النظام:







Micromodule مع FPGA و SDRAM (ابحث عنه على ALI Express بعبارة FPGA AC608) ونفس لوحة ULPI من WaveShare. هذه هي الطريقة التي تبدو بها الوحدة النمطية في الصور من أحد البائعين. أنا كسول جدًا لفكها من العلبة:







بالمناسبة ، فتحات التهوية ، كما في صورة حالتي ، مثيرة جدًا للاهتمام. في النموذج ، ارسم طبقة صلبة ، وفي القطاعة اضبط التعبئة على 40٪ على سبيل المثال وقل أنك تحتاج إلى عمل طبقات صلبة صفرية تحت وما فوق. ونتيجة لذلك ، ترسم الطابعة ثلاثية الأبعاد هذه التهوية نفسها. مريح للغاية.

بشكل عام ، فإن نهج العثور على الأجهزة واضح. الآن نبدأ في تصميم المحلل. بدلاً من ذلك ، قمنا بالفعل بعمل المحلل نفسه في المادتين الأخيرتين ( هنا عملنا مع الأجهزة ، وهنا عملنا للوصول إليه ) ، والآن سنقوم ببساطة بتصميم رأس موجه نحو المشكلة يلتقط البيانات القادمة من ULPI microcircuit.



ما يجب أن يكون الرأس قادرًا على القيام به



في حالة محلل المنطق ، كان كل شيء سهلاً وبسيطًا. هناك بيانات. اتصلنا بهم وبدأنا في التعبئة ، وإرسالهم إلى حافلة AVALON_ST. كل شيء أكثر تعقيدًا هنا. يمكن العثور على مواصفات ULPI هنا . 93 ورقة من النص الممل. أنا شخصياً يقودني هذا إلى اليأس. يبدو وصف شريحة USB3300 ، المثبتة في لوحة WaveShare ، أبسط قليلاً. يمكنك الحصول عليها هنا . على الرغم من أنني ما زلت تراكم الشجاعة منذ ديسمبر 2017 ، إلا أنني أقرأ الوثيقة وأغلقها على الفور ، حيث شعرت بنهج الاكتئاب.



يتضح من الوصف أن ULPI لديها مجموعة من السجلات التي يجب ملؤها قبل بدء العمل. هذا يرجع في المقام الأول إلى سحب المقاومات وإنهائها. هنا صورة لشرح النقطة:







اعتمادًا على الدور (المضيف أو الجهاز) ، بالإضافة إلى السرعة المحددة ، يجب تضمين مقاومات مختلفة. لكننا لسنا مضيف ولا جهاز! يجب فصل جميع المقاومات حتى لا تتداخل مع الأجهزة الرئيسية في الحافلة! يتم ذلك عن طريق الكتابة إلى السجلات.



حسنا ، والسرعة. من الضروري اختيار سرعة العمل. للقيام بذلك ، تحتاج أيضًا إلى الكتابة إلى السجلات.



عندما يتم تكوين كل شيء ، يمكنك البدء في جلب البيانات. ولكن باسم ULPI ، تعني الحروف "LP" "Low Pins". وأدى هذا الانخفاض الشديد في عدد الأرجل إلى مثل هذا البروتوكول الغاضب الذي يتمسك فقط! دعونا نلقي نظرة فاحصة على البروتوكول.



بروتوكول ULPI



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



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







ULPI LINK هو FPGA الخاص بنا.



مخطط توقيت استقبال البيانات



في حالة الراحة ، يجب علينا إصدار 0x00 ثابت لناقل البيانات الذي يتوافق مع الأمر IDLE. إذا كانت البيانات تأتي من ناقل USB ، فسيبدو بروتوكول التبادل كما يلي:







ستبدأ الدورة بحقيقة أن إشارة DIR ستطير إلى واحدة. أولاً ، ستكون هناك دورة ساعة واحدة بحيث يكون لدى النظام الوقت لتغيير اتجاه ناقل البيانات. علاوة على ذلك - تبدأ معجزات الاقتصاد. هل ترى اسم إشارة NXT؟ هذا يعني NEXT عند إرسالها منا. وهنا إشارة مختلفة تمامًا. عندما يكون DIR واحدًا ، سأدعو NXT C / D. مستوى منخفض - لدينا فريق. بيانات عالية.



أي أنه يجب علينا إصلاح 9 بت (ناقل DATA وإشارة NXT) إما دائمًا عند DIR مرتفع (ثم تصفية الساعة الأولى حسب البرنامج) ، أو البدء من الساعة الثانية بعد إقلاع DIR. إذا انخفض خط DIR إلى الصفر ، فإننا نبدل ناقل البيانات للكتابة ونبدأ مرة أخرى في بث الأمر IDLE.



مع استقبال البيانات - من الواضح. الآن دعنا نحلل العمل بالسجلات.



مخطط توقيت الكتابة إلى سجل ULPI



للكتابة على التسجيل ، يتم استخدام المنزل المؤقت التالي (لقد تحولت عمداً إلى المصطلحات ، لأنني أشعر أنني أميل نحو GOST 2.105 ، وهذا ممل ، لذلك سأبتعد عنه):







أولاً وقبل كل شيء ، يجب أن ننتظر الحالة DIR = 0. في الساعة T0 ، يجب علينا ضبط ثابت TXD CMD على ناقل البيانات. ماذا تعني؟ لا يمكنك معرفة ذلك على الفور ، ولكن إذا قمت بالتنقيب قليلاً في المستندات ، اتضح أنه يمكن العثور على القيمة المطلوبة هنا:







أي أنه يجب تعيين بتات البيانات العالية على القيمة "10" (للبايت بأكمله ، القناع هو 0x80) ، والقيم الأقل - رقم التسجيل.



بعد ذلك ، يجب عليك الانتظار حتى تقلع إشارة NXT. بهذه الإشارة ، تؤكد الدائرة أنها سمعتنا. في الصورة أعلاه ، انتظرناها في الساعة T2 وقمنا بتعيين البيانات على الساعة التالية (T3). في الساعة T4 ، سيتلقى ULPI البيانات ويزيل NXT. وسنشير إلى نهاية دورة تبادل الوحدات في STP. في T5 أيضًا ، سيتم حفظ البيانات في السجل الداخلي. لقد انتهت العملية. إليك استرداد لعدد صغير من الاستنتاجات. لكننا سنحتاج إلى كتابة البيانات عند بدء التشغيل فقط ، لذا بالطبع ، سيتعين علينا أن نعاني من التطور ، ولكن كل هذا لن يؤثر بشكل خاص على العمل.



مخطط توقيت القراءة من سجل ULPI



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







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



حسنا؟ دعونا نبدأ بتصميم أوتوماتون سيشكل لنا كل هذا؟



رسم تخطيطي للرأس



كما ترى من الوصف أعلاه ، يجب توصيل الرأس بحافلتين في وقت واحد: AVALON_MM للوصول إلى السجلات و AVALON_ST لإصدار البيانات ليتم تخزينها في ذاكرة الوصول العشوائي. الشيء الرئيسي في الرأس هو الدماغ. ولذا يجب أن تكون آلة الحالة التي ستولد الرسوم البيانية الزمنية التي أخذناها في الاعتبار في وقت سابق.







لنبدأ تطويره بوظيفة استقبال البيانات. يجب أن يوضع في الاعتبار أنه لا يمكننا التأثير على التدفق من حافلة ULPI بأي شكل من الأشكال. البيانات من هناك ، إذا بدأت في الظهور ، فسوف تذهب. لا يهمهم ما إذا كانت حافلة AVALON_ST جاهزة أم لا. لذلك ، سوف نتجاهل ببساطة عدم توفر الحافلة. في محلل حقيقي ، سيكون من الممكن إضافة إشارة إنذار في حالة إخراج البيانات دون جاهزية. يجب أن يكون كل شيء بسيطًا في إطار المقالة ، لذا دعونا نتذكر هذا للمستقبل. ولضمان توفر الحافلة ، كما هو الحال في محلل منطقي ، سيكون لدينا كتلة FIFO خارجية. إجمالاً ، يكون الرسم البياني الانتقالي للموجه الآلي لاستقبال دفق البيانات كما يلي:







أقلعت DIR - بدأت في تلقي. علقنا ساعة واحدة في الانتظار 1 ، ثم نقبلها بينما يساوي DIR ساعة واحدة. سقطت إلى الصفر - بعد ساعة (على الرغم من أنه ليس حقيقة أنها مطلوبة ، ولكن الآن سنقوم بتعيين حالة الانتظار 2) عادت إلى وضع الخمول.



كل شيء بسيط حتى الآن. لا تنس أنه ليس فقط خطوط D0_D7 ، ولكن أيضًا خط NXT يجب أن ينتقل إلى ناقل AVALON_ST ، لأنه يحدد ما يتم نقله الآن: أمر أو بيانات.



يمكن أن يكون لدورة كتابة التسجيل وقت تنفيذ غير متوقع. من وجهة نظر ناقل AVALON_MM ، هذا ليس جيدًا جدًا. لذلك ، سنجعل الأمر أكثر صعوبة. دعونا إنشاء سجل عازلة. سيتم إدخال البيانات إليها ، وبعد ذلك سيتم تحرير ناقل AVALON_MM على الفور. من وجهة نظر الآلية التي يتم تطويرها ، تظهر إشارة الإدخال have_reg (تم استلام البيانات في السجل ، والتي يجب إرسالها) وإشارة الإخراج reg_served (مما يعني أن عملية إصدار السجل قد اكتملت). أضف منطق الكتابة إلى السجل على الرسم البياني الانتقالي للآلة.







لقد أبرزت شرط DIR = 1 باللون الأحمر لتوضيح أن له الأولوية القصوى. ثم من الممكن استبعاد توقع القيمة الصفرية لإشارة DIR في الفرع الجديد من الجهاز. تسجيل الدخول إلى فرع بقيمة مختلفة لن يكون ببساطة ممكنًا. تكون حالة SET_CMDw باللون الأزرق حيث من المرجح أن تكون افتراضية بحتة. هذه مجرد إجراءات يتعين القيام بها! لا أحد يزعج تعيين الثابت المقابل في ناقل البيانات وفقط أثناء النقل! في حالة STPw ، من بين أمور أخرى ، يمكن أيضًا تركيب إشارة reg_served لدورة ساعة واحدة لمسح إشارة BSY لناقل AVALON_MM ، مما يسمح بدورة كتابة جديدة.



حسنًا ، يبقى إضافة فرع لقراءة سجل ULPI. هنا ، العكس هو الصحيح. ترسل لنا آلة خدمة الحافلات طلبًا وتنتظر ردنا. عند استلام البيانات ، يمكنه معالجتها. وستعمل مع تعليق الحافلات أو الاقتراع ، هذه بالفعل مشاكل تلك الآلة. قررت اليوم العمل على مسح. طلب البيانات - ظهر BSY. كيف اختفى BSY - يمكنك تلقي بيانات القراءة. في المجموع ، يأخذ الرسم البياني الشكل:







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



ميزات التنفيذ الآلي من جانب AVALON_MM



عند العمل مع ناقل AVALON_MM ، يمكنك الذهاب بطريقتين. الأول هو إنشاء تأخيرات في الوصول إلى الحافلات. لقد اكتشفنا هذه الآلية في إحدى المقالات السابقة ، وحذرت من أنها محفوفة بالمشاكل. الطريقة الثانية كلاسيكية. أدخل سجل الحالة. في بداية المعاملة ، قم بتعيين إشارة BSY ، عند اكتمالها - إعادة التعيين. وإسناد المسؤولية عن كل شيء إلى المنطق الرئيسي للحافلة (معالج Nios II أو جسر JTAG). كل خيار له مزاياه وعيوبه. نظرًا لأننا فعلنا بالفعل متغيرات مع تأخيرات في الحافلات ، فلنقم بكل شيء اليوم ، من أجل التغيير ، من خلال سجل الحالة.



نقوم بتصميم الجهاز الرئيسي



أول شيء أود أن ألفت انتباهكم إليه هو محفزات RS المفضلة لدي. لدينا جهازان. يخدم الأول حافلة AVALON_MM ، والثاني - واجهة ULPI. اكتشفنا أن الاتصال بينهما يمر عبر علامتين. يمكن كتابة عملية واحدة فقط لكل علم. يتم تنفيذ كل إنسان من خلال عملية خاصة به. كيف تكون؟ لبعض الوقت الآن ، بدأت للتو في إضافة مشغل RS. لدينا بتتان ، لذلك يجب أن يتم إنشاؤها بواسطة قلابين RS. ها هم:

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end


واحد من الديكة reg_served ، والثاني الديوك have_reg. و RS-flip-flop في عمليتها الخاصة تولد إشارة write_busy على أساسها. وبالمثل ، يتكون read_busy من read_finished و reg_request. يمكنك القيام بذلك بشكل مختلف ، ولكن في هذه المرحلة من المسار الإبداعي ، أحب هذه الطريقة.



هذه هي الطريقة التي يتم بها تعيين أعلام BSY. اللون الأصفر لعملية الكتابة ، والأزرق لعملية القراءة. تتميز عملية Verilogov بميزة واحدة مثيرة للغاية. في ذلك ، يمكنك تعيين القيم ليس مرة واحدة ، ولكن عدة مرات. لذلك ، إذا كنت تريد أن تنطلق إشارة لدورة ساعة واحدة ، فأبطلها في بداية العملية (نرى أن كلتا الإشارتين ملغاة هناك) ، واضبطها على واحدة بشرط يتم تنفيذه خلال دورة ساعة واحدة. سيؤدي إدخال الشرط إلى تجاوز الوضع الافتراضي. في جميع الحالات الأخرى ، ستعمل. وبالتالي ، فإن الكتابة إلى منفذ البيانات تبدأ في إقلاع إشارة have_reg لدورة ساعة واحدة ، وتبدأ كتابة البت 0 إلى منفذ التحكم في إقلاع إشارة reg_request.





نفس النص.
//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   






كما رأينا أعلاه ، فإن دورة ساعة واحدة تكفي لضبط RS flip-flop المقابل على واحدة. ومن هذه اللحظة ، تبدأ قراءة إشارة BSY المحددة من سجل الحالة:





نفس النص.
//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   






في الواقع ، بطبيعة الحال تعرفنا على العمليات التي تخدم العمل مع ناقل AVALON_MM.

دعني أذكرك أيضًا بمبادئ العمل مع حافلة ulpi_data. هذه الحافلة ثنائية الاتجاه. لذلك ، يجب عليك استخدام تقنية قياسية للعمل معها. هذه هي الطريقة التي يتم بها الإعلان عن المنفذ المقابل:

   inout        [7:0]  ulpi_data,


يمكننا القراءة من هذه الحافلة ، لكن لا يمكننا الكتابة مباشرة. بدلاً من ذلك ، نقوم بإنشاء نسخة للسجل.

logic [7:0] ulpi_d = 0;


ونوصل هذه النسخة بالحافلة الرئيسية من خلال معدد الإرسال التالي:

//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;


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

النص الكامل للوحدة.
module ULPIhead
(
   input               reset,
   output              clk66,

   // AVALON_MM
   input        [1:0]  address,
   input               write,
   input        [31:0] writedata,
   input               read,
   output logic [31:0] readdata = 0,

   // AVALON_ST
   input  logic        source_ready,
   output logic        source_valid = 0,
   output logic [15:0] source_data = 0,

   // ULPI
   inout        [7:0]  ulpi_data,
   output logic        ulpi_stp = 0,
   input               ulpi_nxt,
   input               ulpi_dir,
   input               ulpi_clk,
   output              ulpi_rst
);

logic      have_reg = 0;
logic      reg_served = 0;
logic      reg_request = 0;
logic      read_finished = 0;
logic [5:0] addr_to_ulpi;
logic [7:0] data_to_ulpi;
logic [7:0] data_from_ulpi;

logic      write_busy = 0;
logic      read_busy = 0;

logic [7:0] ulpi_d = 0;

logic force_reset = 0;

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end

//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   

//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   

//   
enum {idle,
wait1,wr_st,
wait_nxt_w,hold_w,
wait_nxt_r,wait_dir1,latch,wait_dir0

} state = idle;
always_ff @ (posedge ulpi_clk)
begin
   if (reset)
   begin
       state <= idle;
   end else
   begin
      //    
      source_valid <= 0;
      reg_served  <= 0;
      ulpi_stp <= 0;
      read_finished <= 0;
      case (state)
      idle: begin
           if (ulpi_dir)
               state <= wait1;
           else if (have_reg) 
                begin
                  //      , 
                  //    ,   
                  // 
                  ulpi_d [7:6] <= 2'b10;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_w;
                end
           else if (reg_request)
                begin
                  //  -   
                  ulpi_d [7:6] <= 2'b11;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_r;
                end
         end
      //      TURN_AROUND
      wait1 : begin
            state <= wr_st;
            //    ,   
            source_valid <= 1; 
            source_data <= {7'h0,!ulpi_nxt,ulpi_data};
         end
      //     DIR -    AVALON_ST
      wr_st : begin
            if (ulpi_dir)
            begin
              //   ,    
               source_valid <= 1;
               source_data <= {7'h0,!ulpi_nxt,ulpi_data};
            end else
               //      wait2,
               //   ,   - . 
               state <= idle;
         end
      wait_nxt_w : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= data_to_ulpi;
              state <= hold_w;
           end
         end
      hold_w: begin
           //   ,  ULPI 
           //     .   NXT
           //  ...
           if (ulpi_nxt) begin
              // ,  AVALON_MM    
              reg_served  <= 1;
              ulpi_d <= 0;    //   idle
              ulpi_stp <= 1;  //     STP
              state <= idle;  //   -    idle
           end
         end
       //   STPw   ...
       // ...
      //    . ,   NXT
      //    ,    
      wait_nxt_r : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= 0;    //    
              state <= wait_dir1;
           end
         end
      // ,    
      wait_dir1: begin
          if (ulpi_dir)
             state <= latch;
        end
      //    
      //   -   
      latch: begin
          data_from_ulpi <= ulpi_data;
          state <= wait_dir0;
        end
      // ,     
      wait_dir0: begin
          if (!ulpi_dir)
          begin
             state <= idle;
             read_finished <= 1;
          end
        end
   
      default:	begin
         state <= idle;
         end
      endcase
    end
end
//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;

// reset   ,      
assign ulpi_rst = reset | force_reset;

assign clk66 = ulpi_clk;

endmodule




دليل المبرمج



منفذ عنوان تسجيل ULPI (+0)



يجب وضع عنوان سجل حافلة ULPI في المنفذ مع إزاحة +0 ، والتي سيتم استخدامها للعمل



تسجيل بيانات ULPI (+4)



عند الكتابة إلى هذا المنفذ: تبدأ عملية الكتابة إلى سجل ULPI ، التي تم تعيين عنوانها في منفذ عنوان السجل ، تلقائيًا. يحظر الكتابة على هذا المنفذ حتى انتهاء عملية الكتابة السابقة.



عند القراءة: سيعيد هذا المنفذ القيمة التي تم الحصول عليها من آخر قراءة من سجل ULPI.



منفذ تحكم ULPI (+8)



القراءة دائما صفر. تعيين البت للكتابة هو كما يلي:



البت 0 - عند كتابة قيمة واحدة ، يبدأ عملية قراءة سجل ULPI ، الذي يتم تعيين عنوانه في منفذ العناوين في سجل ULPI.



بت 31 - عند كتابة واحدة ، يرسل إشارة RESET إلى رقاقة ULPI.



محجوز بقية البتات.



منفذ الحالة (+ 0x0C)



يقرأ فقط.



بت 0 - WRITE_BUSY. إذا كان يساوي واحدًا ، فإن عملية الكتابة إلى سجل ULPI قيد التقدم.



بت 1 - READ_BUSY. إذا كانت تساوي واحدة ، فإن عملية القراءة من سجل ULPI قيد التقدم.



محجوز بقية البتات.



خاتمة



تعرفنا على طريقة التنظيم المادي لرأس محلل USB ، وصممنا آلية أساسية للعمل مع الدائرة المصغرة ULPI ونفذنا مسودة وحدة SystemVerilog لهذا الرأس. في المقالات التالية ، سنلقي نظرة على عملية النمذجة ، ومحاكاة هذه الوحدة ، ثم إجراء تجارب عملية معها ، وفقًا للنتائج التي سننهيها تمامًا. أي أنه حتى النهاية لدينا أربع مقالات أخرى على الأقل.



All Articles