تنفيذ مخبأ فيريلوج

تتناول هذه المقالة تنفيذ أبسط من ذاكرة الوصول العشوائي في فيريلوج.

قبل متابعة تحليل الرمز ، يوصى بتعلم بناء الجملة الأساسي لـ Verilog.

هنا يمكنك العثور على مواد التدريب .

RAM


الخطوة 1: إعلان الوحدة مع إشارات الإدخال / الإخراج المقابلة


module ram ( input [word_size - 1:0] data, input [word_size - 1:0] addr, input wr, input clk, output response, output [word_size - 1:0] out ); parameter word_size = 32; 

  • البيانات - البيانات في الكتابة.
  • addr - عنوان للذاكرة في ذاكرة الوصول العشوائي.
  • وضع wr (قراءة / كتابة).
  • نظام دورة الساعة clk .
  • استجابة - استعداد RAM (1 - إذا عالجت RAM طلب القراءة / الكتابة ، 0 - خلاف ذلك).
  • خارج - قراءة البيانات من ذاكرة الوصول العشوائي.

تم دمج هذا التطبيق في Altera Max 10 FPGA ، الذي يحتوي على بنية 32 بت ، وبالتالي فإن حجم البيانات والعنوان (word_size) هو 32 بت.

الخطوة 2: إعلان السجلات داخل الوحدة


إعلان صفيف لتخزين البيانات:

 parameter size = 1<<32; reg [word_size-1:0] ram [size-1:0]; 

نحتاج أيضًا إلى تخزين معلمات الإدخال السابقة من أجل تتبع التغييرات في الكتلة دائمًا:

 reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg; 

وآخر تسجيلين لتحديث إشارات الإخراج بعد العمليات الحسابية في الكتلة دائمًا:

 reg [word_size-1:0] out_reg; reg response_reg; 

نهيئ السجلات:

 initial begin response_reg = 1; data_reg = 0; addr_reg = 0; wr_reg = 0; end 

الخطوة 3: تنفيذ المنطق دائما للكتلة


 always @(negedge clk) begin if ((data != data_reg) || (addr%size != addr_reg)|| (wr != wr_reg)) begin response_reg = 0; data_reg = data; addr_reg = addr%size; wr_reg = wr; end else begin if (response_reg == 0) begin if (wr) ram[addr] = data; else out_reg = ram[addr]; response_reg = 1; end end end 

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

منطق خوارزمية الكتلة دائمًا هو كما يلي: إذا تم تحديث البيانات ، فقم بإعادة تعيين حالة الاستعداد إلى 0 وكتابة / قراءة البيانات ، في حالة اكتمال الكتابة / القراءة ، قم بتحديث حالة الاستعداد إلى 1.

في النهاية ، أضف قسم الكود التالي:

 assign out = out_reg; assign response = response_reg; 

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

ذاكرة التخزين المؤقت رسم الخرائط المباشر


التخزين المؤقت التعيين المباشر هو واحد من أبسط أنواع ذاكرة التخزين المؤقت. في هذا التطبيق ، تتكون ذاكرة التخزين المؤقت من عناصر n ، ويتم تقسيم ذاكرة الوصول العشوائي (RAM) بشكل مشروط إلى كتل في n ، ثم العنصر i في th في ذاكرة التخزين المؤقت يناظر جميع عناصر k-th الموجودة في ذاكرة الوصول العشوائي والتي تلبي الشرط i = k٪ n.

تُظهر الصورة أدناه ذاكرة التخزين المؤقت ذات الحجم 4 وذاكرة الوصول العشوائي ذات الحجم 16.



يحتوي كل عنصر من عناصر ذاكرة التخزين المؤقت على المعلومات التالية:

  • بت الصلاحية - ما إذا كانت المعلومات الموجودة في ذاكرة التخزين المؤقت ذات صلة.
  • العلامة هي رقم الكتلة في RAM حيث يوجد هذا العنصر.
  • البيانات - المعلومات التي نكتبها / نقرأها.

عند طلب القراءة ، تقسم ذاكرة التخزين المؤقت عنوان الإدخال إلى جزأين - علامة وفهرس. حجم الفهرس هو log (n) ، حيث n هو حجم ذاكرة التخزين المؤقت.

الخطوة 1: إعلان الوحدة مع إشارات الإدخال / الإخراج المقابلة


 module direct_mapping_cache ( input [word_size-1:0] data, input [word_size-1:0] addr, input wr, input clk, output response, output is_missrate, output [word_size-1:0] out ); parameter word_size = 32; 

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

الخطوة 2: الإعلان عن السجلات وذاكرة الوصول العشوائي


قبل الإعلان عن السجلات ، نحدد حجم ذاكرة التخزين المؤقت والفهرس:

 parameter size = 64; parameter index_size = 6; 

بعد ذلك ، نعلن عن صفيف يتم فيه تخزين البيانات التي نكتبها ونقرأها:

 reg [word_size-1:0] data_array [size-1:0]; 

نحتاج أيضًا إلى تخزين وحدات البت وعلامات الصلاحية لكل عنصر في ذاكرة التخزين المؤقت:

 reg validity_array [size-1:0]; reg [word_size-index_size-1:0] tag_array [size-1:0]; reg [index_size-1:0] index_array [size-1:0]; 

السجلات التي سيتم تقسيم عنوان الإدخال إليها:

 reg [word_size-index_size-1:0] tag; reg [index_size-1:0] index; 

السجلات التي تخزن قيم الإدخال على مدار الساعة السابقة (لتتبع التغييرات في بيانات الإدخال):

 reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg; 

سجلات لتحديث إشارات الإخراج بعد العمليات الحسابية في الكتلة دائما:

 reg response_reg; reg is_missrate_reg; reg [word_size-1:0] out_reg; 

قيم الإدخال لذاكرة الوصول العشوائي:

 reg [word_size-1:0] ram_data; reg [word_size-1:0] ram_addr; reg ram_wr; 

قيم الإخراج لذاكرة الوصول العشوائي:

 wire ram_response; wire [word_size-1:0] ram_out; 

إعلان وحدة ذاكرة الوصول العشوائي وتوصيل إشارات الإدخال والإخراج:

 ram ram( .data(ram_data), .addr(ram_addr), .wr(ram_wr), .clk(clk), .response(ram_response), .out(ram_out)); 

سجل التهيئة:

 initial integer i initial begin data_reg = 0; addr_reg = 0; wr_reg = 0; for (i = 0; i < size; i=i+1) begin data_array[i] = 0; tag_array[i] = 0; validity_array[i] = 0; end end 

الخطوة 3: تنفيذ المنطق دائما للكتلة


بادئ ذي بدء ، لكل ساعة لدينا حالتان - يتم تغيير بيانات الإدخال أو لا تتغير. بناءً على هذا ، لدينا الحالة التالية:

 always @(posedge clk) begin if (data_reg != data || addr_reg != addr || wr_reg != wr) begin end // 1:    else begin // 2:     end end 

كتلة 1. في حالة تغيير بيانات الإدخال ، فإن أول شيء نقوم به هو إعادة تعيين حالة الاستعداد إلى 0:

 response_reg = 0; 

بعد ذلك ، نقوم بتحديث السجلات التي تخزن قيم إدخال الساعة السابقة:

 data_reg = data; addr_reg = addr; wr_reg = wr; 

نقسم عنوان الإدخال إلى علامة وفهرس:

 tag = addr >> index_size; index = addr; 

لحساب العلامة ، يتم استخدام تحول bitwise إلى اليمين ، بالنسبة للمؤشر ، يكفي تعيينه ببساطة ، لأن لا تؤخذ بتات إضافية من العنوان في الاعتبار.

الخطوة التالية هي الاختيار بين الكتابة والقراءة:

 if (wr) begin //  data_array[index] = data; tag_array[index] = tag; validity_array[index] = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end else begin //  if ((validity_array[index]) && (tag == tag_array[index])) begin //    is_missrate_reg = 0; out_reg = data_array[index]; response_reg = 1; end else begin //     is_missrate_reg = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end end 

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

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

 if ((ram_response) && (!response_reg)) begin if (wr == 0) begin validity_array [index] = 1; data_array [index] = ram_out; tag_array[index] = tag; out_reg = ram_out; end response_reg = 1; end 

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

وأخيرًا ، قم بتحديث قيم الإخراج:

 assign out = out_reg; assign is_missrate = is_missrate_reg; assign response = response_reg; 

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


All Articles