تتناول هذه المقالة تنفيذ أبسط من ذاكرة الوصول العشوائي في فيريلوج.
قبل متابعة تحليل الرمز ، يوصى بتعلم بناء الجملة الأساسي لـ 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;