معالج FPGA أصلي مع مترجم لغة عالي المستوى أو Song of the Mouse

تمتلك معالج FPGA الناعم مع مترجم لغة عالي المستوى أو Song of the Mouse - خبرة في تكييف مترجم لغة عالي المستوى مع قلب المعالج المكدس.

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

Python compiler - يبدو أن Uzh مجموعة أدوات سهلة ومريحة لتطوير البرامج لمعالجات البرامج. تسمح مجموعة الأدوات لتعريف الأوليات ووحدات الماكرو كوظائف للغة الهدف بالأماكن المهمة في لغة التجميع للمعالج. تناقش هذه الورقة النقاط الرئيسية لتكييف المترجم لمعالجات بنية المكدس.

بدلًا من النقوش:

إذا كنت تأخذ فأرًا بالغًا
، وتمسكه بحذر ،
فغرز الإبر به
، ستحصل على قنفذ.

إذا كان هذا القنفذ ، سد
الأنف ، حتى لا يتنفس ،
حيث أعمق ، رمي في النهر
سوف تحصل على رافع.

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

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


المقدمة


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

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

قائمة الأمنيات النموذجية


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

الأهداف النموذجية للمعالجات اللينة هي:

  • وظائف كافية لنظام الأوامر ، ربما تم تحسينها للمهمة ؛
  • , .. ;
  • – , .

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

مكونات المصدر


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

مع وجود مهمة إنشاء نظام لجمع البيانات من أجهزة الاستشعار الذكية في وضع قريب من الوقت الفعلي ، تم اختيار معالج Fort كحل مرجعي (ما يسمى بالتصميم المرجعي) للمعالج المرن ، الموصوف في [ 1 ] (سيصبح فيما يلي يُشار إليه أحيانًا بمعالج whiteTiger بالاسم المستعار لمؤلفه).

ميزاته الرئيسية:

  • بيانات منفصلة وإرجاع مكدسات
  • بنية منظمة ذاكرة هارفارد (برنامج وذاكرة بيانات منفصلة ، بما في ذلك مساحة العنوان) ؛
  • التوسع مع الأجهزة الطرفية باستخدام ناقل متوازي بسيط.
  • لا يستخدم المعالج خط أنابيب ، يتم تنفيذ أوامر الدفع والسحب:

    1. جلب الأوامر والمعاملات ؛
    2. تنفيذ الأمر وحفظ النتيجة.

يتم استكمال المعالج بواسطة محمل UART لرمز البرنامج ، والذي يسمح لك بتغيير البرنامج القابل للتنفيذ دون إعادة تجميع المشروع لـ FPGAs.

فيما يتعلق بتكوين ذاكرة الكتلة في FPGA ، يتم تعيين سعة التعليمات إلى 9 بت. يتم تعيين عمق البت للبيانات إلى 32 بت ، ولكن يمكن أن يكون أي بت.

رمز المعالج مكتوب بلغة VHDL دون استخدام أي مكتبات محددة ، مما يسمح لك بالعمل مع هذا المشروع على FPGAs من أي مصنع.

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

بناءً على عدد من العوامل ، تم اختيار لغة Python (Python) للتجربة "لربط" معالج البرامج ومحرك Java Language Engine. هذه لغة برمجة عالية المستوى للأغراض العامة تركز على تحسين إنتاجية المطورين وإمكانية قراءة التعليمات البرمجية ، ودعم العديد من نماذج البرمجة ، بما في ذلك الهيكلية ، والموجهة إلى الكائنات ، والوظيفية ، والضرورية والموجهة نحو الجانب [ 2]].

بالنسبة للمطورين المبتدئين ، فإن امتداد MyHDL [ 3 ، 4 ] مثير للاهتمام ، والذي يسمح بوصف عناصر وهياكل الأجهزة في Python وترجمتها إلى كود VHDL أو Verilog.

منذ بعض الوقت ، تم الإعلان عن مترجم Uzh [ 5 ] - وهو مترجم صغير لمعالج برامج Zmey FPGA (بنية مكدس 32 بت مع دعم متعدد مؤشرات الترابط - إذا اتبعت سلسلة الإصدارات / التعديلات / التحقق - Zmey هو سليل بعيد لمعالج whiteTiger).
Uzh هي أيضًا مجموعة فرعية مجمعة بشكل ثابت من Python ، استنادًا إلى مجموعة أدوات raddsl الواعدة (مجموعة من الأدوات لإنشاء نماذج أولية لمجمعي DSL بسرعة) [ 6 ، 7 ].

وبالتالي ، يمكن صياغة العوامل التي أثرت في اختيار اتجاه العمل على النحو التالي تقريبًا:

  • الاهتمام بالأدوات التي تخفض "عتبة الإدخال" للمطورين المبتدئين للأجهزة والأنظمة على FPGAs (من الناحية النحوية ، فإن Python ليست "مخيفة" بالنسبة للمبتدئين مثل VHDL) ؛
  • السعي لتحقيق الانسجام وأسلوب واحد في المشروع (من الممكن نظريًا وصف كتل الأجهزة والبرمجيات المطلوبة لمعالج البرامج في Python) ؛
  • صدفة عشوائية.

الفروق الدقيقة الصغيرة "تقريبًا" التي لا معنى لها


الكود المصدر لمعالج Zmey ليس مفتوحًا ، ولكن يتوفر وصف لمبادئ تشغيله وبعض ميزات العمارة. على الرغم من أنه قابل للتكديس أيضًا ، إلا أن هناك عددًا من الاختلافات الرئيسية عن معالج whiteTiger:

  • مكدسات هي برمجيات - أي ممثلة بمؤشرات وتوضع في ذاكرة البيانات في عناوين مختلفة ؛
  • , - ;
  • ;
  • , .

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

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

يحتوي مجلد اختبار المترجم على أمثلة لبرامج المعالج البرمجي ؛ ويحتوي المجلد src على النصوص المصدر لعناصر المترجم. للراحة ، من الأفضل إنشاء ملف دفعي صغير (ملحق .cmd) مع المحتويات :، c.py C:\D\My_Docs\Documents\uzh-master\tests\abc.py حيث abc.py هو اسم الملف مع برنامج المعالج الناعم.

ثعبان يعض ذيله أو مكواة وبرمجياته


لتكييف Uzh مع معالج whiteTiger ، ستحتاج إلى بعض التغييرات ، بالإضافة إلى أنه يجب تصحيح المعالج قليلاً.

لحسن الحظ ، لا توجد أماكن كثيرة يمكن تعديلها في المترجم. الملفات الرئيسية "المعتمدة على الأجهزة":

  • asm.py - مجمّع وتشكيل أرقام (حرفية) ؛
  • gen.py - قواعد إنشاء التعليمات البرمجية ذات المستوى المنخفض (الوظائف والمتغيرات والانتقالات والشروط) ؛
  • stream.py - تشكيل تيار التمهيد ؛
  • macro.py - تعريفات الماكرو ، في الواقع - ملحقات اللغة الأساسية بوظائف خاصة بالأجهزة.

في تصميم معالج whiteTiger الأصلي ، يقوم مُحمِّل UART بتهيئة ذاكرة البرنامج فقط. خوارزمية أداة تحميل التشغيل بسيطة ولكنها راسخة وموثوقة:

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

نظرًا لأن المترجم يستخدم أيضًا ذاكرة البيانات ، فمن الضروري تعديل المحمل بحيث يمكنه أيضًا تهيئة ذاكرة البيانات.

نظرًا لأن ذاكرة البيانات متضمنة في منطق نواة المعالج ، فمن الضروري مضاعفة خطوط البيانات والتحكم فيها. لهذا ، يتم إدخال إشارات إضافية DataDinBtemp و LoaderAddrB و DataWeBtemp - إذن بيانات وعنوان وكتابة للمنفذ في الذاكرة.

يبدو رمز محمل الإقلاع الآن كما يلي:

uart_unit: entity work.uart
--uart_unit: entity uart
  Generic map(
    ClkFreq => 50_000_000,
    Baudrate => 115200)
  port map(
    clk => clk,
    rxd => rx,
    txd => tx,
    dout => receivedByte,
    received => received,
    din => transmitByte,
    transmit => transmit);
    
process(clk)
begin
  if rising_edge(clk) then
    if received = '1' then
      case conv_integer(receivedByte) is
      -- 0-F   - 0-3 bits
        when 0 to 15 => CodeDinA(3 downto 0) <= receivedByte(3 downto 0);
		                  DataDinBtemp(3 downto 0) <= receivedByte(3 downto 0);
      -- 10-1F -4-7bits
        when 16 to 31 => CodeDinA(7 downto 4) <= receivedByte(3 downto 0);
		                   DataDinBtemp(7 downto 4) <= receivedByte(3 downto 0); 
      -- 20-2F -8bit 
        when 32 to 47 => CodeDinA(8) <= receivedByte(0);
	                   DataDinBtemp(11 downto 8) <= receivedByte(3 downto 0);
	  when 48 to 63 => DataDinBtemp(15 downto 12) <= receivedByte(3 downto 0);
	  when 64 to 79 => DataDinBtemp(19 downto 16) <= receivedByte(3 downto 0);
	  when 80 to 95 => DataDinBtemp(23 downto 20) <= receivedByte(3 downto 0);
	  when 96 to 111 => DataDinBtemp(27 downto 24) <= receivedByte(3 downto 0);
        when 112 to 127 => DataDinBtemp(31 downto 28) <= receivedByte(3 downto 0);

      -- F0 addr=0
        when 240 => CodeAddrA <= (others => '0');
      -- F1 - WE=1
        when 241 => CodeWeA <= '1';
      -- F2 WE=0 addr++
        when 242 => CodeWeA <= '0'; CodeAddrA <= CodeAddrA + 1;
      -- F3 RESET=1
        when 243 => int_reset <= '1';
      -- F4 RESET=0
        when 244 => int_reset <= '0';

      -- F5 addr=0
        when 245 => LoaderAddrB <= (others => '0');
      -- F6 - WE=1
        when 246 => DataWeBtemp <= '1';
      -- F7 WE=0 addr++
        when 247 => DataWeBtemp <= '0'; LoaderAddrB <= LoaderAddrB + 1;
		  
		  
        when others => null;
      end case;
    end if;
  end if;
end process;

---- end of loader


مع مستوى إعادة تعيين نشط ، يتم توصيل إشارات DataDinBtemp و LoaderAddrB و DataWeBtemp بمنافذ ذاكرة البيانات المقابلة.

if reset = '1' or int_reset = '1' then
      DSAddrA <= (others => '0');      
      
      RSAddrA <= (others => '0');
      RSAddrB <= (others => '0');
      RSWeA <= '0';
      
      DataAddrB <= LoaderAddrB;
		DataDinB<=DataDinBtemp;
		DataWeB<=DataWeBtemp;
      DataWeA <= '0';

وفقًا لخوارزمية أداة تحميل التشغيل ، من الضروري تعديل وحدة stream.py. الآن لديها وظيفتان. الوظيفة الأولى - get_val () - تقسم كلمة الإدخال إلى العدد المطلوب من الرباعيات. لذلك ، للحصول على تعليمات 9 بت من معالج whiteTiger ، سيتم تحويلها إلى مجموعات من ثلاثة رابعين ، وبيانات 32 بت في تسلسل ثمانية رابع. الوظيفة الثانية تجعل () تشكل الحذاء مباشرة.
الشكل النهائي لوحدة الدفق:

def get_val(x, by_4):
  r = []
  for i in range(by_4):
    r.append((x & 0xf) | (i << 4))
    x >>= 4
  return r

def make(code, data, core=0):
  #        0  
  stream = [243,245] 
  for x in data:
    #    32- 
    #         
    stream += get_val(x, 8) + [246, 247]
  #       0
  stream += [240]
  for x in code:
    #    9-  
    #         
    stream += get_val(x, 3) + [241, 242]
  #  
  stream.append(244)

  return bytearray(stream)


ستؤثر التغييرات التالية في المحول البرمجي على وحدة asm.py ، التي تصف نظام أوامر المعالج (يتم كتابة أوامر الاستذكار وأوامر الأمر) وطريقة تمثيل / تجميع القيم العددية - الحرفية.

الأوامر معبأة في قاموس ، والوظيفة lite () هي المسؤولة عن الحرفي. إذا كان كل شيء بسيطًا مع نظام الأوامر - تتغير قائمة فن الإستذكار والرموز العكسية المقابلة ، فإن الوضع بالحرف يختلف قليلاً. يحتوي معالج Zmey على تعليمات 8 بت وهناك عدد من التعليمات المتخصصة للعمل بالحرف. في whiteTiger ، يشير البت التاسع إلى ما إذا كان كود التشغيل أمرًا أو جزءًا من رقم.

إذا كانت أعلى بتة (9) من الكلمة هي 1 ، فسيتم تفسير كود التشغيل كرقم - على سبيل المثال ، أربعة شفرات متتالية مع علامة رقم تشكل رقم 32 بت نتيجة لذلك. علامة نهاية الرقم هي وجود كود تشغيل الأمر - من أجل التحديد وضمان التوحيد ، نهاية تحديد الرقم هي كود تشغيل أمر NOP ("لا توجد عمليات").

ونتيجة لذلك ، تبدو الدالة () المضاءة المعدلة كما يلي:


def lit(x):
  x &= 0xffffffff
  r = [] 
  if (x>>24) & 255 :
    r.append(int((x>>24) & 255) | 256)
  if (x>>16) & 255:
    r.append(int((x>>16) & 255) | 256)
  if (x>>8) & 255:
    r.append(int((x>>8) & 255) | 256)
  r.append(int(x & 255) | 256)
  r += asm("NOP")
  return list(r)


التغييرات / التعريفات الرئيسية والأكثر أهمية في وحدة gen.py. تحدد هذه الوحدة المنطق الأساسي لعمل / تنفيذ التعليمات البرمجية عالية المستوى على مستوى المجمع:

  • قفزات مشروطة وغير مشروطة ؛
  • استدعاء الوظائف وتمرير الحجج إليها ؛
  • العودة من الوظائف وإرجاع النتائج ؛
  • تعديلات على أحجام ذاكرة البرنامج وذاكرة البيانات والأكوام ؛
  • تسلسل الإجراءات عند بدء تشغيل المعالج.

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

في معالج Zmey ، يتم استخدام مكدس الإرجاع للعمل مع المتغيرات المحلية ووسيطات الدوال - يتم نقل وسيطات الدوال إليه وأثناء العمل الإضافي ، يتم الوصول إليها من خلال تسجيل مؤشر مكدس الإرجاع (اقرأ ، عدّل لأعلى / لأسفل ، اقرأ في عنوان المؤشر). نظرًا لأن المكدس موجود فعليًا في ذاكرة البيانات ، فإن هذه العمليات تتعلق ببساطة بعمليات الذاكرة ، وتقع المتغيرات العالمية داخل نفس الذاكرة.

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

للعمل مع المتغيرات المحلية ، تمت إضافة سجل LocalReg مخصص ، وتتمثل مهمته في تخزين مؤشر إلى منطقة الذاكرة المخصصة للمتغيرات المحلية (نوع من الكومة). تمت أيضًا إضافة عمليات للعمل معها (ملف cpu.vhd - منطقة تعريف الأوامر):


          -- group 1; pop 0; push 1;
          when cmdLOCAL => DSDinA <= LocalReg;
			 when cmdLOCALadd => DSDinA <= LocalReg; LocalReg <= LocalReg+1;
			 when cmdLOCALsubb => DSDinA <= LocalReg; LocalReg <= LocalReg-1;
          -- group 2; pop 1; push 0;
          when cmdSETLOCAL => LocalReg <= DSDinA;

LOCAL - يرجع إلى مكدس البيانات القيمة الحالية لمؤشر LocalReg ؛
SETLOCAL - تعيين قيمة المؤشر الجديدة المستلمة من مكدس البيانات ؛
LOCALadd - يترك القيمة الحالية للمؤشر على رصة البيانات ويزيدها بمقدار 1 ؛
LOCALsubb - يترك القيمة الحالية للمؤشر على رصة البيانات
ويقللها بمقدار 1. LOCALadd و LOCALsubb يضافان لتقليل عدد علامات التجزئة أثناء عمليات تمرير معلمات الدالة والعكس بالعكس.

على عكس whiteTiger الأصلي ، تم تغيير اتصالات ذاكرة البيانات قليلاً - الآن يتم معالجة منفذ In memory باستمرار من خلال إخراج الخلية الأولى من رصة البيانات ، يتم تغذية إخراج الخلية الثانية من رصة البيانات إلى إدخالها:

-- ++
DataAddrB <= DSDoutA(DataAddrB'range);
DataDinB <= DSDoutB;

تم أيضًا تصحيح منطق تنفيذ الأمرين STORE و FETCH بشكل طفيف - يتلقى FETCH قيمة إخراج المنفذ في الذاكرة في أعلى حزمة البيانات ، ويتحكم STORE ببساطة في إشارة تمكين الكتابة للمنفذ B:

-- group 3; pop 1; push 1;
          when cmdFETCH => DSDinA <= DataDoutB;
          when cmdSTORE =>            
            DataWeB <= '1';

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

--  
type TCycleStack is array(0 to LocalSize-1) of DataSignal;
signal CycleStack: TCycleStack;
signal CSAddrA, CSAddrB: StackAddrSignal;
signal CSDoutA, CSDoutB: DataSignal;
signal CSDinA, CSDinB: DataSignal;
signal CSWeA, CSWeB: std_logic;
--  
process(clk)
begin
  if rising_edge(clk) then
    if CSWeA = '1' then
      CycleStack(conv_integer(CSAddrA)) <= CSDinA;
      CSDoutA <= CSDinA;
    else
      CSDoutA <= CycleStack(conv_integer(CSAddrA));
    end if;
  end if;
end process;


تمت إضافة أوامر عداد دورة.

DO - ينقل عدد مرات تكرار الدورة من رصة البيانات إلى رصة العداد ويضع القيمة المتزايدة لعداد التعليمات على رصة الإرجاع.

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


	when cmdDO => -- DO - 
               RSAddrA <= RSAddrA + 1; -- 
               RSDinA <= ip + 1;
               RSWeA <= '1';
				
               CSAddrA <= CSAddrA + 1; --
         		CSDinA <= DSDoutA;
 		         CSWeA <= '1';
		         DSAddrA <= DSAddrA - 1; --
		         ip <= ip + 1;	-- 

      when cmdLOOP => --            
           if conv_integer(CSDoutA) = 0 then
	          ip <= ip + 1;	-- 
		         RSAddrA <= RSAddrA - 1; -- 
		         CSAddrA <= CSAddrA - 1; -- 
            else
		         CSDinA <= CSDoutA - 1;
		         CSWeA <= '1';
		         ip <= RSDoutA(ip'range);
            end if;
			 

الآن يمكنك البدء في تعديل الرمز لوحدة gen.py.

* لا تحتاج متغيرات _SIZE إلى تعليقات ولا تتطلب سوى استبدال القيم المحددة في مشروع المعالج الأساسي.

قائمة STUB هي كعب مؤقت لإنشاء مكان لعناوين الانتقال ثم تعبئتها بالمترجم (القيم الحالية تتوافق مع مساحة العنوان 24 بت لذاكرة الكود).

قائمة STARTUP - تعيين تسلسل الإجراءات التي تقوم بها النواة بعد إعادة التعيين - في هذه الحالة ، يتم تعيين عنوان البدء لذاكرة المتغيرات المحلية على 900 ، والانتقال إلى نقطة البداية (إذا لم تقم بتغيير أي شيء ، تتم كتابة نقطة البدء / الإدخال في التطبيق إلى المترجم في عنوان ذاكرة البيانات 2):

STARTUP = asm("""
900  SETLOCAL
2 NOP FETCH JMP
""")

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

@act
def func(t, X):
  t.c.entry = t.c.globs[X]
  t.c.entry["offs"] = len(t.c.code) # - 1
  args = t.c.entry["args"]
  temps_size = len(t.c.entry["locs"]) - args
#      
  t.out = asm("LOCALadd STORE " * args)
  if temps_size:
#      
    t.out += asm("LOCAL %d PLUS SETLOCAL" % temps_size)
  return True

يحدد Epilog () الإجراءات عند العودة من دالة - تحرير ذاكرة المتغيرات المؤقتة ، والعودة إلى نقطة الاستدعاء.

def epilog(t, X):
  locs_size = len(t.c.entry["locs"])
#    
  t.out = asm("RET")
  if locs_size:
#    ()  
    t.out = asm("LOCAL %d MINUS SETLOCAL" % locs_size) + t.out
  return True


يتم العمل مع المتغيرات من خلال عناوينهم ، التعريف الرئيسي لهذا هو push_local () ، والذي يترك عنوان المتغير "عالي المستوى" في رصة البيانات.

def push_local(t, X):
#          
#  
  t.out = asm("LOCAL %d MINUS" % get_loc_offset(t, X))
  return True

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

@act
def goto_if_0(t, X):
  push_label(t, X)
  t.out += asm("IF")
  return True

@act
def goto(t, X):
  push_label(t, X)
  t.out += asm("JMP")
  return True


يحدد التعريفان التاليان عمليات نقل البتات - عند مستوى منخفض فقط ، يتم تطبيق الحلقات (سيعطي بعض الربح في حجم الكود - في الأصل ، يضع المترجم ببساطة العدد المطلوب من عمليات التحول الأولية في صف واحد.

@act
def shl_const(t, X):
  t.out = asm("%d DO SHL LOOP" %(X-1))
  return True

@act
def shr_const(t, X):
  t.out = asm("%d DO SHR LOOP" %(X-1))
  return True

والتعريف الرئيسي للمترجم على مستوى منخفض هو مجموعة من القواعد لعمليات اللغة والعمل مع الذاكرة:

stmt = rule(alt(
  seq(Push(Int(X)), to(lambda v: asm("%d" % v.X))),
  seq(Push(Local(X)), push_local),
  seq(Push(Global(X)), push_global),
  seq(Load(), to(lambda v: asm("NOP FETCH"))),
  seq(Store(), to(lambda v: asm("STORE"))),
  seq(Call(), to(lambda v: asm("CALL"))),
  seq(BinOp("+"), to(lambda v: asm("PLUS"))),
  seq(BinOp("-"), to(lambda v: asm("MINUS"))),
  seq(BinOp("&"), to(lambda v: asm("AND"))),
  seq(BinOp("|"), to(lambda v: asm("OR"))),
  seq(BinOp("^"), to(lambda v: asm("XOR"))),
  seq(BinOp("*"), to(lambda v: asm("MUL"))),
  seq(BinOp("<"), to(lambda v: asm("LESS"))),
  seq(BinOp(">"), to(lambda v: asm("GREATER"))),
  seq(BinOp("=="), to(lambda v: asm("EQUAL"))),
  seq(BinOp("~"), to(lambda v: asm("NOT"))),
  seq(ShlConst(X), shl_const),
  seq(ShrConst(X), shr_const),
  seq(Func(X), func),
  seq(Label(X), label),
  seq(Return(X), epilog),
  seq(GotoIf0(X), goto_if_0),
  seq(Goto(X), goto),
  seq(Nop(), to(lambda v: asm("NOP"))),
  seq(Asm(X), to(lambda v: asm(v.X)))
))

تسمح لك وحدة الماكرو.py "بتوسيع" قاموس اللغة الهدف إلى حد ما باستخدام تعريفات الماكرو في مجمّع المعالج الهدف. بالنسبة إلى برنامج Java Compiler ، لن تختلف التعريفات في macro.py عن عوامل التشغيل "الأصلية" ووظائف اللغة. لذلك ، على سبيل المثال ، في المترجم الأصلي ، تم تحديد وظائف I / O للقيمة في المنفذ الخارجي. تمت إضافة تسلسل اختبار العمليات مع الذاكرة والمتغيرات المحلية وعملية تأخير الوقت.

@macro(1,0)
def testasm(c,x):
  return Asm("1 1 OUTPORT 0 1 OUTPORT 11 10 STORE 10 FETCH 1 OUTPORT  15 100 STORE 100  FETCH 1 OUTPORT")

@macro(1,0)
def testlocal(c,x):
   return Asm("1 100 STORE 2 101 STORE 100 SETLOCAL LOCAL NOP FETCH 1 OUTPORT LOCAL 1 PLUS NOP FETCH 1 OUTPORT")

@prim(1, 0)
def delay(c, val):
  return [val, Asm("DO LOOP")]


اختبارات


يحتوي برنامج اختبار صغير عالي المستوى لمعالجنا على تعريف دالة لحساب عاملي ، والوظيفة الرئيسية التي تنفذ المخرجات التسلسلية لقيم عاملي من 1 إلى 7 إلى المنفذ في حلقة لا نهائية.

def fact(n):
  r = 1
  while n > 1:
    r *= n
    n -= 1
  return r


def main():
  n=1
  while True:
     digital_write(1, fact(n))
     delay(10)
     n=(n+1)&0x7


يمكن تشغيله لتجميعه ، على سبيل المثال ، عن طريق نص بسيط أو من سطر الأوامر بالتسلسل: نتيجة لذلك ، سيتم إنشاء ملف دفق ملف boot.bin ، والذي يمكن نقله إلى قلب المعالج في FPGA عبر المنفذ التسلسلي (في الحقائق الحديثة ، من خلال أي منفذ تسلسلي افتراضي توفره المحولات واجهات USB-UART). ونتيجة لذلك يشغل البرنامج 146 كلمة (9 بت) من ذاكرة البرنامج و 3 في ذاكرة البيانات.
c.py C:\D\My_Docs\Documents\uzh-master\tests\fact2.py




استنتاج


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

نتائج تجميع المعالج (سعة 32 بت ، كلمات 4K لذاكرة البرنامج و 1 K RAM) لسلسلة FPGA Altera Cyclone V تعطي ما يلي:

Family	Cyclone V
Device	5CEBA4F23C7
Logic utilization (in ALMs)	694 / 18,480 ( 4 % )
Total registers	447
Total pins	83 / 224 ( 37 % )
Total virtual pins	0
Total block memory bits	72,192 / 3,153,920 ( 2 % )
Total DSP Blocks	2 / 66 ( 3 % )

المؤلفات

  1. المعالج الرابع على VHDL // m.habr.com/en/post/149686
  2. Python - ويكيبيديا // en.wikipedia.org/wiki/Python
  3. نبدأ FPGA على Python _ Habr // m.habr.com/en/post/439638
  4. MyHDL // www.myhdl.org
  5. جيثب - صحيح، grue_uzh_ Uzh مترجم // github.com/true-grue/uzh
  6. GitHub - true-grue_raddsl_ أدوات للنماذج الأولية السريعة لمجمعي DSL // github.com/true-grue/raddsl
  7. sovietov.com/txt/dsl_python_conf.pdf

المؤلف ممتن لمطوري معالج برامج Zmey ومترجم Uzh للاستشارات والصبر.

All Articles