باستخدام Quartus و ModelSim

لطالما كنت مهتمًا بالدوائر الرقمية ، ولا سيما لغات وصف الأجهزة - HDL. كان لدي كتاب من تأليف ديفيد م. هاريس وسارة ل. هاريس ، "الدائرة الرقمية وهندسة الكمبيوتر" ، على قائمة القراءة المستقبلية لفترة طويلة ، مستفيدين من وقت الفراغ في العزلة الذاتية ، وصلت إلى هذا الكتاب الرائع. في عملية القراءة ، واجهت بعض الصعوبات ، وعلى وجه الخصوص ، كيفية كتابة وتصحيح التعليمات البرمجية في Quartus Prime. في عملية البحث ، ساعدني موقع marsohod.org كثيرًا، ولكن تم وصف عملية محاكاة الدائرة على هذا الموقع باستخدام أدوات Quartus المضمنة وفي الإصدارات الحديثة من البرنامج ، هذه الأدوات المدمجة غير موجودة وتحتاج إلى استخدام ModelSim. من أجل تنظيم المعرفة التي اكتسبتها باستخدام Quartus و ModelSim بطريقة ما ، قررت كتابة هذه المقالة. في سياق هذه المقالة ، كمثال ، سأقوم بتحليل المشكلة من كتاب "الدوائر الرقمية وهندسة الكمبيوتر" من تأليف ديفيد م. هاريس وسارة ل.هاريس ، على وجه التحديد المشكلة 3.26 حول آلة المياه الغازية. طوال المقالة سأوضح كيفية تثبيت Quartus وإنشاء مشروع وكتابة التعليمات البرمجية ومحاكاة ذلك. كل من يهتم بذلك ، مرحبًا به تحت القطة.



صورة



صياغة المشكلة



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



افترض أنه لكل إشارة ساعة يتم إدخال عملة واحدة فقط. تحتوي الماكينة على مخرجات: صب الصودا وإرجاع 1 روبل وإرجاع 2 روبل وإرجاع 2 مقابل 2 روبل. بمجرد أن يتم جمع 5 روبل (أو أكثر) في آلة البيع ، تقوم بتعيين الإشارة "تعبئة الغاز" ، وكذلك إشارات عودة التغيير المقابل. ثم يجب أن تكون الآلة جاهزة لقبول العملات مرة أخرى.



نظرية



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



  • كل عنصر من عناصر الدائرة هو إما تسجيل أو دائرة متكاملة.
  • عنصر مخطط واحد على الأقل هو التسجيل.
  • يتم تسجيل جميع التسجيلات بإشارة ساعة واحدة.
  • يحتوي كل مسار دوري على سجل واحد على الأقل.


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

رمز وصف
ق 0 الحالة الأولية ، المبلغ المتراكم 0 روبل.
ق 1 المبلغ المتراكم 1 فرك.
ق 2 تراكمت 2 روبل.
ق 3 تراكمت 3 روبل.
ق 4 تراكمت 4 روبل.


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

رمز القيمة وصف
أنا 1 01 1 فرك
أنا 2 عشرة 2 فرك
أنا 5 أحد عشر 5 روبل روسي


دعنا نرسم رسم تخطيطي لحالة إنساننا (على الرسوم البيانية للحالة لـ Mealy automaton ، من الضروري الإشارة إلى إشارات الإخراج على أسهم انتقال الحالة ، ولن أفعل ذلك حتى لا أتعثر في الرسم البياني ، سيتم وصف جميع إشارات الإخراج في الجدول أدناه):



صورة



دعنا نكتب جدول التغييرات في الحالات وإشارات الإخراج:

تنص على إشارات الإدخال
S S' insert pour_water C 1 . change1 2 . change2 2 2 . change22
S0 S1 I1 0 0 0 0
S0 S2 I2 0 0 0 0
S0 S0 I5 1 0 0 0
S1 S2 I1 0 0 0 0
S1 S3 I2 0 0 0 0
S1 S0 I5 1 1 0 0
S2 S3 I1 0 0 0 0
S2 S4 I2 0 0 0 0
S2 S0 I5 1 0 1 0
S3 S4 I1 0 0 0 0
S3 S0 I2 1 0 0 0
S3 S0 I5 1 1 1 0
S4 S0 I1 1 0 0 0
S4 S0 I2 1 1 0 0
S4 S0 I5 1 0 0 1




Quartus Prime



يحتوي Quartus على إصدار Lite مجاني ، والذي يحتوي على بعض القيود مقارنة بالإصدار الاحترافي ، والحد الأقصى الرئيسي ليس أكثر من 10000 سطر من شفرة المصدر لمحاكاة مشروع. تنزيله ، بعد التسجيل ، يمكنك اتباع الرابط ، في وقت كتابة أحدث إصدار كان 19.1 ، بناءً على العمل مع هذا الإصدار ، كتبت مقالًا. نختار Lite Edition ، الإصدار 19.1 ، نظام التشغيل Windows (تجدر الإشارة إلى أن هناك إصدارًا من Quartus لنظام Linux ويعمل بشكل جيد ، تنشأ مشاكل مع ModelSim ، وهو 32 بت ويستخدم إصدارًا قديمًا من مكتبة عرض الخطوط ، لذلك في البداية أوصي باستخدام إصدار Windows ) ، حدد علامة التبويب الملفات المدمجة. حجم أرشيف التنزيل كبير جدًا - 5.6 غيغابايت ، ضع هذا في الاعتبار. قم بتوسيع الأرشيف الذي تم تنزيله وتشغيلهsetup.bat . يتم التثبيت بطريقة قياسية ، نستخدم التحديد الافتراضي للمكونات.



إنشاء المشروع



لإنشاء مشروع جديد ، حدد ملف -> الجديد ... بواسطة Project Wizard . نافذة المعالج الأولى إعلامية ، انقر فوق التالي ، في النافذة الثانية ، حدد مكان المشروع ، واسمه "soda_machine" وعنصر التصميم عالي المستوى "soda_machine" ، كما في الصورة:



صورة



في النافذة التالية ، حدد "Empty project" . نافذة لإضافة ملفات "إضافة ملفات" ، لا تقم بإضافة أي شيء. تعتبر نافذة اختيار جهاز "العائلة والأجهزة وإعدادات اللوحة" مهمة جدًا لمشروع حقيقي ، ولكن نظرًا لأن مشروعنا بعيد عن الواقع ، نترك الإعدادات الافتراضية هنا ، كما في الشكل:



صورة



نافذة لتحديد إعدادات الأدوات الأخرى"إعدادات أداة EDA" ، حدد لمحاكاة المشروع لاستخدام "ModelSim-Altera " وتنسيق "System Verilog HDL" كما في الصورة:



صورة



نافذة المعلومات الأخيرة "الملخص" ، انقر فوق " إنهاء" .



كتابة كود المصدر



سيكون لدينا ملفان رئيسيان مع شفرة المصدر ، هذه هي وحدة soda_machine نفسها ومنضدة الاختبار الخاصة بها ، وكلا هذين الملفين سيستخدمان نوع البيانات insert_type ، الذي يصف كيفية ترميز فئات العملات المعدنية ومن المنطقي فصلها إلى ملف منفصل. ولكن هناك بعض الصعوبات المرتبطة بميزات التجميع لـ Quartus و ModelSim. يقوم Quartus بتجميع جميع ملفات المصدر في مسار واحد ، و ModelSim يقوم بتجميع كل ملف على حدة ، لذلك عند تجميع Quartus لن يكون هناك إعادة تعريف لنوع insert_type ، استخدمت التقنية من C / C ++ تتضمن حارسًا بناءً على توجيهات المعالج الدقيق. بالإضافة إلى ذلك ، لكي يتأكد ModelSim من أن insert_type المستخدم في وحدة soda_machineوفي منصة الاختبار ، واحد ونفس ، ضع وصفه داخل حزمة soda_machine_types . بالنظر إلى هذه المتطلبات ، يكون ملف soda_machine_types.sv كما يلي:



soda_machine_types.sv
`ifndef soda_machine_types_sv_quard

package soda_machine_types;

	typedef enum logic [1:0] {I1=2'b01, I2=2'b10, I5=2'b11} insert_type;
	
endpackage

`define soda_machine_types_sv_quard
`endif




توجد الآن وحدة soda_machine نفسها في ملف soda_machine.sv :



soda_machine.sv
`include "soda_machine_types.sv"
import soda_machine_types::*;


module soda_machine(
	input logic clk,          // Clock 
	input logic reset,        // Active high level
	input insert_type insert,
	output logic pour_water,
	output logic change1,
	output logic change2,
	output logic change22);

	typedef enum logic [2:0] {S0, S1, S2, S3, S4} state_type;
	(* syn_encoding = "default" *) state_type state, nextstate;
	//       
	always_ff @(posedge clk, posedge reset)
	if (reset)
		state <= S0;
	else
		state <= nextstate;
	//            
	always_comb
		case (state)
			S0:
				case (insert)
					I1:
						nextstate = S1;
					I2:
						nextstate = S2;
					I5:
						nextstate = S0;
				endcase
			S1: 
				case (insert)
					I1:
						nextstate = S2;
					I2:
						nextstate = S3;
					I5:
						nextstate = S0;
				endcase
			S2:
				case (insert)
					I1:
						nextstate = S3;
					I2: 
						nextstate = S4;
					I5:
						nextstate = S0;
				endcase
			S3: 
				if (insert == I1)
					nextstate = S4;
				else
					nextstate = S0;
			S4:
				nextstate = S0;
		endcase
	//    
	assign pour_water = (state == S4) | (insert == I5) | (state == S3) & (insert == I2);
	
	assign change1 = (state == S1) & (insert == I5) | (state == S3) & (insert == I5) | (state == S4) & (insert == I2);
							
	assign change2 = (state == S2) & (insert == I5) | (state == S3) & (insert == I5);
	
	assign change22 = (state == S4) & (insert == I5);
	
endmodule




كيف سيتم ترميز حالة آلة الدولة ، تركتها حتى Quartus. للإشارة إلى كيفية إجراء الترميز بالضبط ، يتم استخدام السمة (* syn_encoding = "default" *) ، يمكن رؤية خيارات الترميز الأخرى هنا .



وتجدر الإشارة إلى أنه في مشروع حقيقي ، يجب تخزين إشارات الإخراج للمنطق التوافقي من Mealy automaton في سجلات ، ومن مخرجات التسجيل بالفعل ، يتم تغذيتها إلى إخراج FPGA. يجب مزامنة إشارات الإدخال مع تردد الساعة باستخدام المزامنات ، لتجنب الوقوع في حالة عدم الاستقرار.



لإضافة ملفات إلى المشروع ، استخدم ملف -> جديد "SystemVerilog HDL File"وعند الحفظ أعطي الاسم المناسب. بعد إضافة هذين الملفين ، يمكن تجميع المشروع معالجة -> بدء التجميع . بعد التجميع الناجح ، يمكنك رؤية أدوات المخطط الناتجة -> Netlist Viewers -> RTL Viewer :



عارض RTL
image



لعرض أدوات حالة الجهاز -> عارض Netlist -> عارض آلة الدولة



عارض آلة الدولة
image



في علامة تبويب التشفير ، يمكنك أن ترى أن Quartus قام بتطبيق نظام الترميز "ساخن واحد" ، وذلك عندما يتم استخدام D-flip-flop منفصل لكل حالة ، ويتم ترميز حالة S 0 كـ 0 ، وليس 1 كما هو الحال بالنسبة للحالات الأخرى ، يتم ذلك لتبسيط دائرة إعادة التعيين إلى الأولى حالة. قد تلاحظ أن RTL Viewer لا يعرض مخططًا تخطيطيًا بالضبط ، بل مفهومًا. لعرض الرسم التخطيطي ، استخدم أدوات -> Netlist Viewrs -> Technology Map Viewer (Post-Fitting)



محاكاة



من حيث المبدأ ، لدينا في الوقت الحالي رسم تخطيطي لآلة بيع لبيع المياه الفوارة ، لكننا بحاجة إلى التأكد من أنها تعمل بشكل صحيح ، لهذا سنكتب منضدة اختبار ونضعها في ملف soda_machine_tb.sv :



soda_machine_tb.sv
`include "soda_machine_types.sv"
import soda_machine_types::*;

module soda_machine_tb;

	insert_type insert;
	
	logic [5:0] testvectors[10000:0];
	
	int vectornum, errors;
	
	logic clk, reset, pour_water, change1, change2, change22;
	logic pour_water_expected, change1_expected, change2_expected, change22_expected;
	//  
	soda_machine dut(
		.clk(clk),
		.reset(reset),
		.insert(insert),
		.pour_water(pour_water),
		.change1(change1),
		.change2(change2),
		.change22(change22)
	);
	//   
	always
		#5 clk = ~clk;
	//   
	initial begin
		//     
		$readmemb("../../soda_machine.tv", testvectors);
		vectornum = 0;
		errors = 0;
		clk = 1;
		//   
		reset = 1; #13; reset = 0;
	end
	//   
	always @(posedge clk) begin
		#1; {insert, pour_water_expected, change1_expected, change2_expected, change22_expected} = testvectors[vectornum];
	end
	// ,      
	always @(negedge clk)
		if (~reset) begin
			if ((pour_water !== pour_water_expected) || (change1 !== change1_expected) || (change2 !== change2_expected) ||
				(change22 !== change22_expected)) begin
				$error("%3d test insert=%b\noutputs pour_water=%b (%b expected), change1=%b (%b expected), change2=%b (%b expected), change22=%b (%b expected)", 
					vectornum + 1, insert, pour_water, pour_water_expected, change1, change1_expected, change2, change2_expected, change22, change22_expected);
				errors = errors + 1;
			end
			vectornum = vectornum + 1;
			if (testvectors[vectornum] === 6'bx) begin
				$display("Result: %3d tests completed with %3d errors", vectornum, errors);
				$stop;
			end
		end

endmodule




لاختبار وحدتنا ، نستخدم ملف متجه اختبار soda_machine.tv :



soda_machine.tv
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
01_1_0_0_0
10_0_0_0_0
10_0_0_0_0
10_1_1_0_0
11_1_0_0_0
10_0_0_0_0
10_0_0_0_0
11_1_0_0_1
10_0_0_0_0
11_1_0_1_0
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
11_1_1_1_0




البتتان الأوليان هما إدخال المدخلات ، والبتات الأربعة التالية هي توقعاتنا للنواتج: pour_water ، change1 ، change2 ، change22. على سبيل المثال ، في بداية الملف ، يتم إدخال عملة روبل 5 مرات متتالية ، على العملة الخامسة ، ننتظر ظهور إشارة pour_water ، بينما تكون إشارات التغيير غير نشطة. تمت إضافة ملف soda_machine.tv إلى ملف المشروع -> "ملف نصي" جديد لتسهيل



العمل مع ModelSim ، أضف ملف soda_machine_run_simulation.do بالمحتوى التالي:



soda_machine_run_simulation.do
add wave /soda_machine_tb/dut/clk
add wave /soda_machine_tb/dut/reset
add wave /soda_machine_tb/dut/insert
add wave /soda_machine_tb/dut/state
add wave /soda_machine_tb/dut/nextstate
add wave /soda_machine_tb/dut/pour_water
add wave /soda_machine_tb/dut/change1
add wave /soda_machine_tb/dut/change2
add wave /soda_machine_tb/dut/change22
view structure
view signals
run -all
wave zoom full




ستقوم بتشغيل محاكاة لدينا وعرض الأشكال الموجية في ModelSim. تمت إضافة ملف soda_machine_run_simulation.do إلى ملف المشروع -> جديد "ملف البرنامج النصي Tcl"



الآن سنقوم بإعداد المشروع بحيث تبدأ المحاكاة تلقائيًا. حدد عنصر القائمة تعيينات -> إعدادات ، حدد فئة إعدادات أداة EDA -> محاكاة . في إعدادات NativeLink ، حدد Compile test bench: وانقر فوق الزر Test Benches ... في نافذة Test Benches التي تفتح ، انقر فوق الزر New ... في نافذة New Test Bench Settings التي تفتح ، املأ حقل اسم منضدة الاختبار: soda_machine_tbوانقر على زر تحديد الملف ... في أسفل النافذة ، حدد ملف soda_machine_tb.sv وانقر على الزر إضافة . يجب أن يبدو بالشكل التالي:



صورة



في نافذة New Test Bench Settings ، انقر فوق OK . في إطار اختبار البدلاء يجب أن تبدو مثل هذا:

صورة



في اختبار مقاعد النافذة، انقر OK . في إعدادات NativeLink ، قم بتعيين مربع الاختيار Use script لإعداد المحاكاة وحدد ملف soda_machine_run_simulation.do . يجب أن تبدو نافذة الإعدادات

كما يلي:



صورة



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



علامة التبويب نسخة ModelSim
image



يتم تمييز إخراج منضدة الاختبار الخاصة بنا على عدد الاختبارات التي تم إجراؤها والأخطاء المكتشفة باللون الأحمر. ظهور علامة تبويب الموجة:



علامة تبويب WaveSim Wave
image



كود مصدر المشروع



شفرة المصدر من هذا المشروع هو في github.com/igoral5/soda_machine استنساخ المشروع، ثم فتح المشروع باستخدام كوارتس ملف -> فتح المشروع ...

تحديد soda_machine.qpf ملف . ثم قم بتجميع معالجة المشروع -> بدء التجميع وبدء أدوات المحاكاة -> تشغيل أداة المحاكاة -> محاكاة RTL .



All Articles