عمل عملي مع FPGAs في مجموعة Redd. إتقان DMA لحافلة Avalon-ST والتبديل بين حافلات Avalon-MM

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

للقيام بذلك ، سوف نتقن كتلة DMA. بشكل عام ، DMA هو موضوعي المفضل. حتى أنني قمت بمقالة رائعة حول DMA على بعض وحدات تحكم ARM . من هذه المقالة يتضح أن DMA يأخذ دورات الساعة من الحافلة. في المقالة الحالية ، سننظر في كيفية سير الأمور مع نظام المعالج المستند إلى FPGA.




إنشاء الأجهزة


نبدأ في إنشاء الأجهزة. من أجل فهم مدى تعارض كتلة DMA مع دورات الساعة ، سنحتاج إلى أخذ قياسات دقيقة عند التحميل العالي على ناقل Avalon-MM (Avalon Memory-Mapped). لدينا بالفعل اكتشفت أن الجسر ألتيرا JTAG إلى أفالون-MM لا يمكن أن توفر الأحمال حافلة عالية. لذلك ، علينا اليوم إضافة نواة معالج إلى النظام حتى يصل إلى الناقل بسرعة عالية. تم وصف كيفية القيام بذلك هنا . من أجل المثالية ، دعنا نعطل كلا المخبئين المؤقتين لمعالج المعالج ، ولكن ننشئ حافلة متصلة بقوة ، كما فعلنا هنا .



أضف 8 كيلو بايت من ذاكرة البرنامج وذاكرة البيانات. تذكر أن الذاكرة يجب أن تكون ثنائية المنفذ ولها عنوان في نطاق خاص (لمنعها من القفز ، قفلها ، ناقشنا أسباب ذلك هنا ).



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

القاعدة جاهزة. الآن نحن بحاجة إلى مصدر بيانات سنضعه في الذاكرة. الشيء المثالي هو مؤقت موقوتة باستمرار. إذا لم تتمكن كتلة DMA خلال إجراء ما من معالجة البيانات ، فسنرى ذلك على الفور بالقيمة المفقودة. حسنًا ، هذا إذا كانت هناك قيم 1234 و 1236 في الذاكرة ، فهذا يعني أنه على مدار الساعة ، عندما أصدر المؤقت 1235 ، لم تقم كتلة DMA بنقل البيانات. قم بإنشاء ملفTimer_ST.sv بمثل هذا العداد البسيط:

module Timer_ST (
  input              clk,
  input              reset,
	
  input  logic       source_ready,
  output logic       source_valid,
  output logic[31:0] source_data
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

endmodule

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

الآن نقوم بإنشاء مكون خاص بنا (يتم شرح كيفية القيام بذلك هنا ). اختار الأتمتة عن طريق الخطأ حافلة Avalon_MM بالنسبة لنا. استبدلها بـ avalon_streaming_source وقم بتعيين الإشارات كما هو موضح أدناه:



عظيم. أضف مكوننا إلى النظام. الآن نحن نبحث عن كتلة DMA ... ولا نجد واحدة ، بل ثلاثة. كلهم موصوفون في الوثيقة Embedded Peripheral IP User Guide من Altera (كما هو الحال دائمًا ، أعطي الأسماء ، ولكن ليس الروابط ، نظرًا لأن الروابط تتغير دائمًا).



أيهما تستخدم؟ لا استطيع مقاومة الحنين. في عام 2012 ، أنشأت نظامًا يعتمد على ناقل PCIe. تحتوي جميع الأدلة من Altera على مثال يستند إلى أول هذه الكتل. لكنه مع مكون PCIe أعطى سرعة لا تزيد عن 4 ميغابايت في الثانية. في تلك الأيام ، بصقت وكتبت كتلة DMA الخاصة بي. الآن لا أتذكر سرعته ، لكنه قاد البيانات من محركات أقراص SATA إلى حدود إمكانات محركات الأقراص ومحركات أقراص الحالة الصلبة في تلك الأوقات. أي ، لقد قمت بشحذ سن على هذه الكتلة. لكنني لن أخوض في مقارنة الكتل الثلاث. والحقيقة هي أنه يتعين علينا اليوم العمل مع مصدر يعتمد على Avalon-ST (واجهة تدفق أفالون) ، وكتلة Modular Scatter-Gather DMA فقط تدعم هذه المصادر . هنا نضعها على الرسم البياني.

في إعدادات الحظر ، حدد الوضعالجري إلى الذاكرة المعينة . بالإضافة إلى ذلك - أريد توجيه البيانات من الإطلاق إلى ملء SDRAM ، لذلك استبدلت الحد الأقصى لوحدة نقل البيانات من 1 كيلوبايت إلى 4 ميجابايت. صحيح ، لقد تم تحذيري من أنه في النهاية ، لن تكون معلمة FMax ساخنة جدًا (حتى إذا قمت باستبدال الحد الأقصى للكتلة بـ 2 كيلوبايت). لكن اليوم ، FMax مقبول (104 MHz) ، ثم سنكتشف ذلك. تركت المعلمات المتبقية دون تغيير. يمكنك أيضًا ضبط وضع الإرسال على Full Word Access فقط ، سيؤدي هذا إلى زيادة FMax إلى 109 MHz. لكننا لن نقاتل من أجل الأداء اليوم.



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

نظرًا لأن وحدة تحكم SDRAM هي منفذ واحد ، يمكن أيضًا استخدام الذاكرة المدمجة حصريًا في وضع المنفذ الواحد. انه مهم. والحقيقة هي أننا نريد أن نكتب إلى الذاكرة باستخدام سيد كتلة DMA ، ولكن من ناحية أخرى ، نريد أن نقرأ من هذه الذاكرة باستخدام قلب المعالج أو كتلة Altera JTAG-to-Avalon-MM. اليد تمد وربط كتل الكتابة والقراءة بمنفذين مختلفين ... ولكن لا يمكنك ذلك! بدلا من ذلك ، ممنوع في ظروف المشكلة. لأنه من الممكن اليوم ، ولكن غدًا سنستبدل الذاكرة بذاكرة أحادية المنفذ حصريًا. بشكل عام ، نحصل على مثل هذه الكتلة من ثلاثة مكونات (المؤقت و DMA والذاكرة):



حسنًا ، ولمجرد الشكل المبدئي ، سأضيف UT و sysid إلى نظام JTAG (على الرغم من أن النظام الثاني لم يساعد ، كان لا يزال عليّ أن أتحمل مع محول JTAG على أي حال). ما هو ، وكيف تحل إضافتهم المشاكل الصغيرة ، لقد درسنا بالفعل. لن أقوم بتلوين الإطارات ، كل شيء واضح معهم. فقط أظهر كيف يبدو كل شيء في مشروعي:



هذا كل شيء. النظام جاهز. نقوم بتعيين العناوين ، وتعيين ناقلات المعالج ، وإنشاء نظام (لا تنس أنك تحتاج إلى الحفظ بنفس اسم المشروع نفسه ، ثم ستنتقل إلى المستوى الأعلى من التسلسل الهرمي) ، نضيفه إلى المشروع. اجعل ساق إعادة الضبط ظاهريًا ، وقم بتوصيل clk بـ pin_25 leg. نحن نجمع المشروع ، ونصبه في المعدات ... كيف هو ، أيها المسكين ، في المكتب الخالي بسبب الموقع البعيد الكلي؟ .. إنه وحيد ومخيف بالنسبة لها ، على الأرجح ، بمفردها ... لكنني كنت مشتتًا.

إنشاء جزء برنامج


تدريب


في محرر BSP مع حركة اليد المعتادة ، أقوم بتشغيل دعم C ++. غالبًا ما أدرجت لقطة شاشة لهذه الحالة لدرجة أنني أتوقف عن فعلها. لكن لقطة شاشة أخرى ، على الرغم من رؤيتها بالفعل ، لا تزال شائعة جدًا. لذلك دعونا نناقشها مرة أخرى. نتذكر أن النظام يحاول وضع البيانات في أكبر جزء من الذاكرة. وهذا هو العازلة . لذلك ، نفرض كل شيء على البيانات :



تجربة البرنامج


نصنع رمزًا يملأ ببساطة الذاكرة بمحتويات المصدر (في دور العداد).

عرض الكود
#include "sys/alt_stdio.h"
#include <altera_msgdma.h>
#include <altera_msgdma_descriptor_regs.h>
#include <system.h>
#include <string.h>

int main()
{ 
  alt_putstr("Hello from Nios II!\n");

  memset (BUFFER_BASE,0,BUFFER_SIZE_VALUE);

  //  ,   
  IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
      ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK);

  //   ,      ,
  //    ,   .  .

  //    FIFO
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_READ_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)0);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_WRITE_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)BUFFER_BASE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_LENGTH(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      BUFFER_SIZE_VALUE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_CONTROL_STANDARD(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      ALTERA_MSGDMA_DESCRIPTOR_CONTROL_GO_MASK);

   //  ,    
   IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
       ALTERA_MSGDMA_CSR_STOP_ON_ERROR_MASK
       & (~ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK)
       &(~ALTERA_MSGDMA_CSR_GLOBAL_INTERRUPT_MASK)) ;


   //   
   static const alt_u32 errMask = ALTERA_MSGDMA_CSR_STOPPED_ON_ERROR_MASK |
           ALTERA_MSGDMA_CSR_STOPPED_ON_EARLY_TERMINATION_MASK |
           ALTERA_MSGDMA_CSR_STOP_STATE_MASK |
           ALTERA_MSGDMA_CSR_RESET_STATE_MASK;

  volatile alt_u32 status;
  do
  {
     status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));     

  alt_putstr("You can play with memory!\n");

  /* Event loop never exits. */
  while (1);

  return 0;
}


نبدأ ، ننتظر رسالة "يمكنك اللعب بالذاكرة!" ، ضع البرنامج على وقفة وانظر إلى الذاكرة ، بدءًا من العنوان 0. في البداية كنت خائفة للغاية:



من العنوان 0x80 ، يغير العداد قيمته بشكل حاد. علاوة على ذلك ، كمية كبيرة جدا. ولكن اتضح أن كل شيء على ما يرام. في مكاننا ، لا يتوقف العداد أبدًا وهو جاهز دائمًا ، ولدى DMA قائمة انتظار القراءة الخاصة به. دعني أذكرك بإعدادات كتلة DMA:



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



لا أثق بعيني ، لقد كتبت رمزًا يتحقق تلقائيًا من البيانات:

  volatile alt_u32* pData = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 cur = pData[0x10];
  int nLine = 0;
  for (volatile int i=0x11;i<BUFFER_SIZE_VALUE/4;i++)
  {
	  if (pData[i]!=cur+1)
	  {
		  alt_printf("Problem at 0x%x\n",i*4);
		  if (nLine++ > 10)
		  {
			  break;
		  }
	  }
	  cur = pData[i];
  }

كما أنه لا يكشف عن أي مشاكل.

محاولة إيجاد بعض المشاكل على الأقل


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



كل شيء يستمر في العمل! حسن. تأكد من أننا نتسبب في عدد مرات الوصول إلى الحافلة أكثر من البعد FIFO. إضافة عداد ضرب:



نفس النص:
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


في نهاية العمل ، يبلغ 29. هذا هو أكثر من 16. أي أنه يجب تجاوز الـ FIFO. فقط في حالة ، دعنا نضيف المزيد من قراءات تسجيل الحالة. لا يساعد.

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

   output    clk_copy,
   output    ready_copy

وعينهم:

    assign clk_copy = clk;
    assign ready_copy = source_ready;

ونتيجة لذلك ، بدأت الوحدة تبدو كما يلي:

module Timer_ST (
   input           clk,
   input           reset,
	
   input logic     source_ready,
   output logic    source_valid,
   output logic[31:0] source_data,

   output    clk_copy,
   output    ready_copy
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

    assign clk_copy = clk;
    assign ready_copy = source_ready;

endmodule

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

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



بعد ذلك ، لدينا صورة جميلة في 40960 نانو ثانية ، أي 2048 دورة (مع بلورة منزلية ، يجب تخفيض المخزن المؤقت إلى 8 كيلوبايت ، أي 2048 كلمة اثنين وثلاثين بتًا). ها هي بدايتها:



ها هي النهاية:



حسنًا ، طوال الوقت - ليس فشلًا واحدًا. لا ، كان من الواضح أن هذا سيحدث ، ولكن كان هناك بعض الأمل ...

ربما علينا أن نحاول الكتابة في الحافلة ، وليس فقط القراءة منها؟ أضفت كتلة GPIO إلى النظام: أضفت



إدخالًا له أثناء انتظار الاستعداد:



نفس النص
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00);

       n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


لا توجد مشاكل وهذا كل شيء! على من يقع اللوم؟

لا توجد معجزات ، ولكن هناك أشياء غير مكتشفة


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

ولكن دعونا نلقي نظرة على الرسائل التي تظهر عند إنشاء النظام. تمييز الكلمات الرئيسية:



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



الصورة المفتوحة مثيرة للإعجاب في حد ذاتها ، لكنني لن أعطيها. وعلى الفور



سأحدد علامة التبويب هذه: وهناك نرى ما يلي:



سأعرض أكبرها أهمية:



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

نحن نثير مشاكل حقيقية


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



نفس النص
  volatile alt_u32 status;
  volatile int n = 0;
  volatile alt_u32* pBuf = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 sum = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  sum += pBuf[n];

	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));


وأخيرًا ، ظهرت الانخفاضات على الموجة!



وجدت وظيفة فحص الذاكرة أيضًا الكثير من الإغفالات:



نعم ، ونحن نرى جيدًا أن البيانات يتم تحويلها من صف إلى صف:



وهنا مثال على بقعة سيئة معينة (6CCE488F مفقود):



الآن نرى أن التجربة قد تمت بشكل صحيح ، تم تنفيذ بيئة التطوير فقط التحسين بالنسبة لنا. هذا هو الحال عندما تنطق عبارة "الكل يؤذي كل الفولاذ بذكاء" ليس بالسخرية ، ولكن بالامتنان. شكرا لمطوري Quartus على هذه المسألة!

استنتاج


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

في المقالة التالية ، سنستبدل RAM بوحدة تحكم SDRAM ، والمؤقت مع "رأس" حقيقي ونصنع أول محلل منطقي. هل ستعمل؟ أنا لا أعرف حتى الآن. آمل ألا تظهر المشاكل.

All Articles