فيريلوج آخر بسيط المعالج

توضح المقالة المعالج البدائي التالي وتجميع له.
بدلاً من RISC / CISC المعتادة ، لا يحتوي المعالج على مجموعة من التعليمات في حد ذاتها ، هناك فقط نسخة واحدة من التعليمات.


معالجات مماثلة لديها سلسلة مكسيم MAXQ .


أولاً ، دعنا نصف ROM ، ذاكرة البرنامج


module rom1r(addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input [ADDR_WIDTH - 1 : 0] addr_r; output [DATA_WIDTH - 1 : 0] data_r; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; initial $readmemh("rom.txt", mem, 0, (1<<ADDR_WIDTH) - 1); assign data_r = mem[addr_r]; endmodule 

ذاكرة الوصول العشوائي مزدوجة المنفذ لذاكرة البيانات


 module ram1r1w(clk_wr, addr_w, data_w, addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input clk_wr; input [ADDR_WIDTH - 1 : 0] addr_r, addr_w; output [DATA_WIDTH - 1 : 0] data_r; input [DATA_WIDTH - 1 : 0] data_w; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; assign data_r = mem[addr_r]; always @ (posedge clk_wr) mem[addr_w] <= data_w; endmodule 

والمعالج نفسه


 module cpu(clk, reset, port); parameter WIDTH = 8; parameter RAM_SIZE = WIDTH; parameter ROM_SIZE = WIDTH; input clk, reset; output [WIDTH-1 : 0] port; 

يحتاج كحد أدنى إلى سجل عداد الأوامر ، بالإضافة إلى سجل إضافي واحد ، وكذلك سجل منفذ IO ، بحيث يكون هناك شيء يجب أن يظهر من معالجنا.


  reg [WIDTH-1 : 0] reg_pc; reg [WIDTH-1 : 0] reg_reg; reg [WIDTH-1 : 0] reg_port; assign port = reg_port; 

سيكون عداد الأوامر هو عنوان ذاكرة البرنامج.


  wire [WIDTH-1 : 0] addr_w, addr_r, data_r, data_w, data; rom1r rom (reg_pc, {addr_w, addr_r}); defparam rom.ADDR_WIDTH = ROM_SIZE; defparam rom.DATA_WIDTH = RAM_SIZE * 2; 

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


  ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); defparam ram.ADDR_WIDTH = RAM_SIZE; defparam ram.DATA_WIDTH = WIDTH; 

نشير إلى عناوين خاصة: عداد أوامر ، مولد ثابت ، فحص لـ 0 (للقفزات الشرطية) ، عمليات الجمع / الطرح ، ومنفذ الإدخال والإخراج ، في هذه الحالة ، يتم إخراج فقط.


  parameter PC = 0; parameter CG = 1; parameter TST = 2; parameter ADD = 3; parameter SUB = 4; parameter PORT = 5; 

حافلات البيانات لمنفذي الذاكرة ليست مترابطة فقط ، ولكن من خلال المضاعفات ، والتي ستكون في نفس الوقت بمثابة ALU.


مُضاعِف واحد - على ناقل البيانات لمنفذ القراءة ، من أجل قراءة عداد الأوامر (للتحولات النسبية) ، IO ، وما إلى ذلك بدلاً من الذاكرة في عناوين محددة


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


  assign data = (addr_r == PC) ? reg_pc : (addr_r == PORT) ? reg_port : data_r; assign data_w = (addr_w == CG) ? addr_r : (addr_w == TST) ? |data : (addr_w == ADD) ? data + reg_reg : (addr_w == SUB) ? data - reg_reg : data; 

لا يمكن الوصول مباشرة إلى السجل المساعد reg_reg ، المستخدم في العمليات الحسابية ، ولكن يتم نسخ نتيجة كل تعليمة إليه.


وبالتالي ، لإضافة قيمتين من الذاكرة ، يجب عليك أولاً قراءة واحدة منها في أي مكان ، على سبيل المثال ، قم بنسخها إلى نفسك (وفي الوقت نفسه في reg_reg) ، وسيقوم أمر الكتابة التالي في عنوان adder بتسجيل المبلغ بالقيمة السابقة هناك.


منشئ ثابت يكتب العنوان ، وليس قيمة الذاكرة ، إلى هذا العنوان.


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


  always @ (posedge clk) begin if (reset) begin reg_pc <= 0; end else begin reg_reg <= data_w; if (addr_w == PC) begin reg_pc <= data_w; end else begin reg_pc <= reg_pc + (((addr_w == TST) && data_w[0]) ? 2 : 1); case (addr_w) PORT: reg_port <= data_w; endcase end end end endmodule 

cpu.v
 module rom1r(addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input [ADDR_WIDTH - 1 : 0] addr_r; output [DATA_WIDTH - 1 : 0] data_r; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; initial $readmemh("rom.txt", mem, 0, (1<<ADDR_WIDTH) - 1); assign data_r = mem[addr_r]; endmodule module ram1r1w(write, addr_w, data_w, addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input write; input [ADDR_WIDTH - 1 : 0] addr_r, addr_w; output [DATA_WIDTH - 1 : 0] data_r; input [DATA_WIDTH - 1 : 0] data_w; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; assign data_r = mem[addr_r]; always @ (posedge write) mem[addr_w] <= data_w; endmodule module cpu(clk, reset, port); parameter WIDTH = 8; parameter RAM_SIZE = 8; parameter ROM_SIZE = 8; parameter PC = 0; parameter CG = 1; parameter TST = 2; parameter ADD = 3; parameter SUB = 4; parameter PORT = 5; input clk, reset; output [WIDTH-1 : 0] port; wire [WIDTH-1 : 0] addr_r, addr_w, data_r, data_w, data; reg [WIDTH-1 : 0] reg_pc; reg [WIDTH-1 : 0] reg_reg; reg [WIDTH-1 : 0] reg_port; assign port = reg_port; rom1r rom(reg_pc, {addr_w, addr_r}); defparam rom.ADDR_WIDTH = ROM_SIZE; defparam rom.DATA_WIDTH = RAM_SIZE * 2; ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); defparam ram.ADDR_WIDTH = RAM_SIZE; defparam ram.DATA_WIDTH = WIDTH; assign data = (addr_r == PC) ? reg_pc : (addr_r == PORT) ? reg_port : data_r; assign data_w = (addr_w == CG) ? addr_r : (addr_w == TST) ? |data : (addr_w == ADD) ? data + reg_reg : (addr_w == SUB) ? data - reg_reg : data; always @ (posedge clk) begin if (reset) begin reg_pc <= 0; end else begin reg_reg <= data_w; if (addr_w == PC) begin reg_pc <= data_w; end else begin reg_pc <= reg_pc + (((addr_w == TST) && data_w[0]) ? 2 : 1); case (addr_w) PORT: reg_port <= data_w; endcase end end end endmodule 

هذا هو المعالج كله.


المجمع


الآن سنكتب له برنامجًا بسيطًا يقوم ببساطة بإخراج القيم إلى المنفذ بالتتابع ويتوقف عند 5.


لقد كان كسولًا جدًا أن تكتب المجمّع بنفسك ، حتى لو كان هذا بسيطًا (بناء الجملة بأكمله A = B) ، لذلك بدلاً من ذلك ، تم استخدام لغة Lua الجاهزة كأساس ، وهو مناسب تمامًا لبناء العديد من لغات النطاق المحددة استنادًا إلى ذلك ، وفي نفس الوقت نحصل على مُعالجة Lua سابقة التجهيز مجانًا. .


أولاً ، الإعلان عن عناوين خاصة ، الإدخال الذي يغير فيه البيانات ومتغير العداد في العنوان 7


 require ("asm") PC = mem(0) CG = mem(1) TST = mem(2) ADD = mem(3) SUB = mem(4) PORT = mem(5) cnt = mem(7) 

بدلاً من وحدات الماكرو ، يمكنك استخدام وظائف Lua المعتادة ، على الرغم من حقيقة أنه تم تغيير جدول تعريف بيئة _G للقبض على الواجبات (انظر أدناه) ، سقطت المتغيرات العامة في نفس الوقت: إعلان المتغير غير المحلي some_variable = 0xAA يعتبر المجمع الخاص به ملكًا له ويحاول تحليله بدلاً من ذلك ، للإعلان عن متغير عام preprocessor ، يجب عليك استخدام rawset (_G ، some_variable ، 0xAA) ، والذي لا يلمس metamethods.


 function jmp(l) CG = l PC = CG end 

سيتم الإشارة إلى التصنيفات بواسطة تسمية الكلمة وثوابت السلسلة ، في Lua ، في حالة وسيطة سلسلة واحدة ، يمكن حذف وظيفة القوس.


 label "start" 

صفر عداد المنفذ وتسجيل:


 CG = 0 cnt = CG PORT = CG 

في الحلقة ، قم بتحميل الثابت 1 ، وأضفه إلى متغير العداد وأظهره في المنفذ:


 label "loop" CG = 1 ADD = cnt -- add = cnt + 1 cnt = ADD PORT = ADD 

أضف الواحد المفقود إلى السعة الزائدة عند 0 ، وإذا لم يكن هناك صفر ، فانتقل إلى البداية ، وتخطي CG = "exit" ، وإلا فإننا ننتهي في حلقة "لانهائية".


 CG = -5 ADD = ADD --add = add + 251 CG = "loop" TST = ADD --skip "exit" if not 0 CG = "exit" PC = CG label "exit" jmp "exit" 

test.lua
 require ("asm") PC = mem(0) CG = mem(1) TST = mem(2) ADD = mem(3) SUB = mem(4) PORT = mem(5) cnt = mem(7) function jmp(l) CG = l PC = CG end label "start" CG = 0 cnt = CG PORT = CG label "loop" CG = 1 ADD = cnt -- add = cnt + 1 cnt = ADD PORT = ADD CG = -5 ADD = ADD --add = add + 256 - 5 CG = "loop" TST = ADD --skip "exit" if not 0 CG = "exit" PC = CG label "exit" jmp "exit" 

والآن المجمع asm.lua نفسه ، كما ينبغي أن يكون في 20 خطوط:


في دالة mem (لإعلان عناوين خاصة) ، يضيف الشخص أيضًا التعيين التلقائي للعنوان المجاني التالي ، إذا لم يتم تحديد وسيطة.
وبالنسبة للعلامات ، ستحتاج إلى التحقق من إعادة الإعلان عن علامة موجودة


 local output = {} local labels = {} function mem(addr) return addr end function label(name) labels[name] = #output end 

لا يحتوي Lua على metamethod للتخصيص ، ولكن هناك metamethods لفهرسة القيم الحالية ولإضافة قيم جديدة ، بما في ذلك جدول البيئة العالمي _G.
نظرًا لأن __newindex يعمل فقط من أجل القيم غير الموجودة في الجدول ، بدلاً من إضافة عناصر جديدة إلى _G ، فأنت بحاجة لإخفائها في مكان ما دون إضافة إلى _G ، وبالتالي ، أخرجها عند الوصول إليها عبر __index.


إذا كان الاسم موجودًا بالفعل ، فقم بإضافة هذه التعليمات إلى الباقي.


 local g = {} setmetatable(_G, { __index = function(t, k, v) return g[k] end, __newindex = function(t, k, v) if g[k] then table.insert(output, {g[k], v}) else g[k]=v end end }) 

حسنًا ، بعد تنفيذ برنامج المجمّع ، عندما يصل جامع البيانات المهملة أخيرًا للصفيف مع برنامج الإخراج الخاص بنا ، نقوم فقط بطباعته ، مع استبدال عناوين النص بالعناوين الصحيحة في نفس الوقت.


 setmetatable(output, { __gc = function(o) for i,v in ipairs(o) do if type(v[2]) == "string" then v[2] = labels[v[2]] or print("error: ", v[2]) end print(string.format("%02X%02X", v[1] & 0xFF, v[2] & 0xFF)) end end }) 

asm.lua
 local output = {} local labels = {} function mem(addr) return addr end function label(name) labels[name] = #output end local g = {} setmetatable(_G, { __index = function(t, k, v) return g[k] end, __newindex = function(t, k, v) if g[k] then table.insert(output, {g[k], v}) else g[k]=v end end }) setmetatable(output, { __gc = function(o) for i,v in ipairs(o) do if type(v[2]) == "string" then v[2] = labels[v[2]] or print("error: ", v[2]) end print(string.format("%02X%02X", v[1] & 0xFF, v[2] & 0xFF)) --FIX for WIDTH > 8 end end }) 

من خلال تشغيل lua53 test.lua> rom.txt ( أو عبر الإنترنت ) نحصل على برنامج للمعالج في رموز الجهاز.


rom.txt
 0100 0701 0501 0101 0307 0703 0503 01FB 0303 0103 0203 010D 0001 010D 0001 

للمحاكاة ، سنقوم بإجراء اختبار بسيط لا يُصدر سوى إعادة التعيين ويسحب القطع.


test.v
 `include "cpu.v" module test(); reg clk; reg reset; wire [7:0] port; cpu c(clk, reset, port); initial begin $dumpfile("test.vcd"); reset <= 1; clk <= 0; #4 reset <= 0; #150 $finish; end always #1 clk <= !clk; endmodule 

بعد محاكاة استخدام iverilog -o test.vvp test.v ، افتح test.vcd الناتج في GTKWave:

يتم احتساب المنفذ إلى خمسة ، ثم حلقات المعالج.


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

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


All Articles