
مع تطور الإلكترونيات الدقيقة ، أصبحت تصميمات RTL أكثر فأكثر. إعادة استخدام كود فيريلوج هو الكثير من الإزعاج ، حتى مع توليد ، وحدات الماكرو ورقائق فيريلوج النظام. ومع ذلك ، فإن إزميل يجعل من الممكن تطبيق القوة الكاملة للكائنات والبرمجة الوظيفية على تطوير rtl ، وهي خطوة طال انتظارها يمكن أن تملأ رئتي مطوري ASIC و FPGA بالهواء النقي.
ستقدم هذه المقالة نظرة عامة موجزة على الوظائف الرئيسية وستنظر في بعض حالات الاستخدام للمستخدم ، وسنتحدث أيضًا عن أوجه القصور في هذه اللغة. في المستقبل ، إذا كان الموضوع مثيرًا للاهتمام ، فإننا نواصل المقالة في دروس أكثر تفصيلاً.
متطلبات النظام
- مستوى قاعدة سكالا
- فيريلوج والمبادئ الأساسية لبناء التصاميم الرقمية.
- حافظ على وثائق إزميل في متناول اليد
سأحاول فهم أساسيات الإزميل باستخدام أمثلة بسيطة ، ولكن إذا كان هناك شيء غير واضح ، يمكنك إلقاء نظرة هنا .
أما بالنسبة إلى السكالا ، فقد تساعد ورقة الغش هذه في الغوص السريع.
هناك واحد مماثل للإزميل .
يمكن العثور على كود المقالة الكامل (في شكل مشروع scala sbt) هنا .
عداد بسيط
كما يوحي الاسم ، فإن إزميل "بناء الأجهزة في لغة سكالا المضمنة" هو عبارة عن لغة وصف للأجهزة مبنية على أعلى سكالا.
باختصار حول كيفية عمل كل شيء ، ثم: يتم إنشاء رسم بياني للأجهزة من وصف rtl على الإزميل ، والذي يتحول بدوره إلى وصف وسيط في لغة firrtl ، وبعد ذلك يتم إنشاء مترجم الخلفية المدمج من firrtl verilog.
دعونا نلقي نظرة على تطبيقين للعداد البسيط.
فيريلوج:
module SimpleCounter #( parameter WIDTH = 8 )( input clk, input reset, input wire enable, output wire [WIDTH-1:0] out ); reg [WIDTH-1:0] counter; assign out = counter; always @(posedge clk) if (reset) begin counter <= {(WIDTH){1'b0}}; end else if (enable) begin counter <= counter + 1; end endmodule
إزميل:
class SimpleCounter(width: Int = 32) extends Module { val io = IO(new Bundle { val enable = Input(Bool()) val out = Output(UInt(width.W)) }) val counter = RegInit(0.U(width.W)) io.out <> counter when(io.enable) { counter := counter + 1.U } }
قليلا عن الإزميل:
Module
- حاوية لوصف وحدة RTLBundle
هي بنية بيانات في الإزميل ، تستخدم بشكل أساسي لتحديد الواجهات.io
- متغير لتحديد المنافذBool
- نوع البيانات ، إشارة بسيطة أحادية البتUInt(width: Width)
- عدد صحيح بدون إشارة ، يقبل المنشئ عمق البت للإشارة كمدخل.RegInit[T <: Data](init: T)
هو مُنشئ تسجيل ؛ يأخذ قيمة إعادة تعيين عند الإدخال وله نفس نوع البيانات.<>
- عامل توصيل إشارة عالميwhen(cond: => Bool) { /*...*/ }
- التناظرية if
في فيريلوج
سنتحدث عن أي فيريلوج يولد إزميلًا بعد ذلك بقليل. الآن فقط قارن بين هذين التصميمين. كما ترى ، لا يوجد أي إشارة إلى إشارات clk
وإعادة reset
في الإزميل. والحقيقة هي أن الإزميل يضيف هذه الإشارات إلى الوحدة بشكل افتراضي. يتم RegInit
قيمة إعادة تعيين تسجيل counter
RegInit
مُنشئ التسجيل مع إعادة تعيين RegInit
. يحتوي الإزميل على دعم الوحدات مع العديد من إشارات الساعة ، ولكن بعد ذلك بقليل.
العداد أكثر تعقيدًا قليلاً
دعنا نذهب إلى أبعد من ذلك وتعقد المهمة قليلاً ، على سبيل المثال - سنقوم بعمل عداد متعدد القنوات مع معلمة إدخال في شكل سلسلة من البتات لكل قناة.
لنبدأ الآن بإصدار الإزميل
class MultiChannelCounter(width: Seq[Int] = Seq(32, 16, 8, 4)) extends Module { val io = IO(new Bundle { val enable = Input(Vec(width.length, Bool())) val out = Output(UInt(width.sum.W)) def getOut(i: Int): UInt = { val right = width.dropRight(width.length - i).sum this.out(right + width(i) - 1, right) } }) val counters: Seq[SimpleCounter] = width.map(x => Module(new SimpleCounter(x)) ) io.out <> util.Cat(counters.map(_.io.out)) width.indices.foreach { i => counters(i).io.enable <> io.enable(i) } }
قليلا عن سكالا:
width: Seq[Int]
- معلمة الإدخال MultiChannelCounter
فئة MultiChannelCounter
، لها النوع Seq[Int]
- تسلسل بعناصر صحيحة.Seq
هو أحد أنواع المجموعات في سكالا مع تسلسل محدد للعناصر..map
هي وظيفة مألوفة للمجموعات للجميع ، قادرة على تحويل مجموعة إلى أخرى بسبب نفس العملية على كل عنصر ، في حالتنا تتحول سلسلة من القيم الصحيحة إلى سلسلة من SimpleCounter
مع عمق البت المقابل.
قليلا عن الإزميل:
Vec[T <: Data](gen: T, n: Int): Vec[T]
- نوع بيانات الإزميل ، هو تناظري للصفيف.Module[T <: BaseModule](bc: => T): T
هي طريقة الغلاف المطلوبة Module[T <: BaseModule](bc: => T): T
.util.Cat[T <: Bits](r: Seq[T]): UInt
- دالة تسلسل ، تناظرية {1'b1, 2'b01, 4'h0}
في {1'b1, 2'b01, 4'h0}
انتبه للموانئ:
enable
- تم نشره بالفعل في Vec[Bool]
* ، تقريبًا ، في مصفوفة من إشارات بت واحد ، واحدة لكل قناة ، كان من الممكن جعل UInt(width.length.W)
.
out
- تم توسيعه ليصبح مجموع عرض كل قنواتنا.
counters
المتغيرة هي مجموعة من عداداتنا. نقوم بتوصيل إشارة enable
لكل عداد بمنفذ الإدخال المقابل ، util.Cat
جميع الإشارات الخارجية في واحدة باستخدام وظيفة util.Cat
المضمنة.
نلاحظ أيضًا getOut(i: Int)
- تقوم هذه الوظيفة بحساب وإرجاع نطاق البتات في إشارة out
للقناة i
. سيكون مفيدًا جدًا في المزيد من العمل مع مثل هذا العداد. تنفيذ شيء من هذا القبيل في فيريلوج لن ينجح
* لا ينبغي الخلط بين Vec
Vector
، الأول هو مجموعة من البيانات في الإزميل ، والثاني هو مجموعة في سكالا.
دعونا نحاول الآن كتابة هذه الوحدة على verilog ، للراحة ، حتى على systemVerilog.
بعد الجلوس في التفكير ، جئت إلى هذا الخيار (على الأرجح أنه ليس الخيار الوحيد الحقيقي والأفضل ، ولكن يمكنك دائمًا اقتراح التنفيذ في التعليقات).
فيريلوج module MultiChannelCounter #( parameter TOTAL = 4, parameter integer WIDTH_SEQ [TOTAL] = {32, 16, 8, 4} )(clk, reset, enable, out); localparam OUT_WIDTH = get_sum(TOTAL, WIDTH_SEQ); input clk; input reset; input wire [TOTAL - 1 : 0] enable; output wire [OUT_WIDTH - 1 :0] out; genvar j; generate for(j = 0; j < TOTAL; j = j + 1) begin : counter_generation localparam OUT_INDEX = get_sum(j, WIDTH_SEQ); SimpleCounter #( WIDTH_SEQ[j] ) SimpleCounter_unit ( .clk(clk), .reset(reset), .enable(enable[j]), .out(out[OUT_INDEX + WIDTH_SEQ[j] - 1: OUT_INDEX]) ); end endgenerate function automatic integer get_sum; input integer array_width; input integer array [TOTAL]; integer counter = 0; integer i; begin for(i = 0; i < array_width; i = i + 1) counter = counter + array[i]; get_sum = counter; end endfunction endmodule
يبدو بالفعل أكثر إثارة للإعجاب. ولكن ماذا لو ذهبنا إلى أبعد من ذلك ونقرّب واجهة عظم الترقوة الشهيرة مع إمكانية الوصول إلى التسجيل.
واجهات حزمة
Wishbone عبارة عن حافلة صغيرة مشابهة لـ AMBA APB ، تستخدم بشكل أساسي لأنوية IP مفتوحة المصدر.
مزيد من التفاصيل حول الويكي: https://ru.wikipedia.org/wiki/Wishbone
لأن يزودنا الإزميل بحاويات بيانات من نوع Bundle
، فمن المنطقي أن نلف الحافلة في حاوية يمكن استخدامها لاحقًا في أي مشاريع إزميل.
class wishboneMasterSignals( addrWidth: Int = 32, dataWidth: Int = 32, gotTag: Boolean = false) extends Bundle { val adr = Output(UInt(addrWidth.W)) val dat_master = Output(UInt(dataWidth.W)) val dat_slave = Input(UInt(dataWidth.W)) val stb = Output(Bool()) val we = Output(Bool()) val cyc = Output(Bool()) val sel = Output(UInt((dataWidth / 8).W)) val ack_master = Output(Bool()) val ack_slave = Input(Bool()) val tag_master: Option[UInt] = if(gotTag) Some(Output(Bool())) else None val tag_slave: Option[UInt] = if(gotTag) Some(Input(Bool())) else None def wbTransaction: Bool = cyc && stb def wbWrite: Bool = wbTransaction && we def wbRead: Bool = wbTransaction && !we override def cloneType: wishboneMasterSignals.this.type = new wishboneMasterSignals(addrWidth, dataWidth, gotTag).asInstanceOf[this.type] }
قليلا عن سكالا:
Option
- مجمع بيانات اختياري في سكالا يمكن أن يكون إما عنصرًا أو None
، Option[UInt]
إما Some(UInt(/*...*/))
أو بلا ، مفيد عند تحديد معلمات الإشارات.
يبدو أنه لا يوجد شيء غير عادي. مجرد وصف للواجهة بواسطة المعالج ، باستثناء بعض الإشارات والأساليب:
tag_master
و tag_slave
هي tag_slave
اختيارية للأغراض العامة في بروتوكول عظم الترقوة ، وسوف نراها إذا كانت معلمة gotTag
true
.
wbTransaction
، wbWrite
، wbRead
- وظائف لتبسيط العمل مع الناقل.
cloneType
- طريقة الاستنساخ من النوع المطلوب لجميع فئات المعلمات [T <: Bundle]
لكننا نحتاج أيضًا إلى واجهة تابعة ، دعنا نرى كيف يمكن تنفيذها.
class wishboneSlave( addrWidth: Int = 32, dataWidth: Int = 32, tagWidht: Int = 0) extends Bundle { val wb = Flipped(new wishboneMasterSignals(addrWidth , dataWidth, tagWidht)) override def cloneType: wishboneSlave.this.type = new wishboneSlave(addrWidth, dataWidth, tagWidht).asInstanceOf[this.type] }
إن الطريقة Flipped
، كما قد تخمن من الاسم ، تقلب الواجهة ، والآن تحولت واجهة المعالج إلى عبد ، نضيف نفس الفئة للمعالج.
class wishboneMaster( addrWidth: Int = 32, dataWidth: Int = 32, tagWidht: Int = 0) extends Bundle { val wb = new wishboneMasterSignals(addrWidth , dataWidth, tagWidht) override def cloneType: wishboneMaster.this.type = new wishboneMaster(addrWidth, dataWidth, tagWidht).asInstanceOf[this.type] }
حسنًا ، هذا كل شيء ، الواجهة جاهزة. ولكن قبل كتابة معالج ، دعنا نرى كيف يمكننا استخدام هذه الواجهات في حال احتجنا إلى التبديل أو شيء مع مجموعة كبيرة من واجهات عظم الترقوة.
class WishboneCrossbarIo(n: Int, addrWidth: Int, dataWidth: Int) extends Bundle { val slaves = Vec(n, new wishboneSlave(addrWidth, dataWidth, 0)) val master = new wishboneMaster(addrWidth, dataWidth, 0) } class WBCrossBar extends Module { val io = IO(new WishboneCrossbarIo(1, 32, 32)) io.master <> io.slaves(0)
هذا فارغ صغير للمفتاح. من الملائم الإعلان عن واجهة من النوع Vec[wishboneSlave]
، ويمكنك توصيل الواجهات بنفس عامل التشغيل <>
. رقائق إزميل مفيدة عندما يتعلق الأمر بإدارة مجموعة كبيرة من الإشارات.
تحكم عالمي للحافلات
كما ذكرنا سابقًا حول قوة البرمجة الوظيفية وبرمجة الكائن ، سنحاول تطبيقه. علاوة على ذلك ، سنتحدث عن تنفيذ وحدة التحكم في ناقل الترقوة العالمي في شكل trait
، سيكون نوعًا من الخلط لأي وحدة نمطية مع ناقل wishboneSlave
، بالنسبة للوحدة التي تحتاجها فقط لتحديد بطاقة ذاكرة ومزج وحدة تحكم trait
إليها أثناء التوليد.
التنفيذ
بالنسبة لأولئك الذين لا يزالون متحمسيندعنا ننتقل إلى تنفيذ المعالج. سيكون الأمر بسيطًا ويستجيب على الفور للمعاملات الفردية ، في حالة الخروج من مجموعة العناوين ، يتم إرجاع صفر.
دعونا نحلل في أجزاء:
يجب الرد على كل معاملة مع الإقرار
val io : wishboneSlave = val wb_ack = RegInit(false.B) when(io.wb.wbTransaction) { wb_ack := true.B }.otherwise { wb_ack := false.B } wb_ack <> io.wb.ack_slave
- نرد على القراءة بالبيانات
val wb_dat = RegInit(0.U(io.wb.dat_slave.getWidth.W))
MuxCase[T <: Data] (default: T, mapping: Seq[(Bool, T)]): T
هو مخطط التنسيق المدمج لنوع case
في verilog *.
كيف سيبدو في فيريلوج:
always @(posedge clock) if(reset) wb_dat_o <= 0; else if(wb_read) case (wb_adr_i) `ADDR_1 : wb_dat_o <= data_1; `ADDR_2 : wb_dat_o <= data_2; `ADDR_3 : wb_dat_o <= data_3; default : wb_dat_o <= 0; endcase }
* بشكل عام ، في هذه الحالة ، يعد هذا اختراقًا صغيرًا من أجل وضع المعايير ، في إزميل يوجد تصميم قياسي أفضل للاستخدام إذا كتبت شيئًا أبسط.
switch(x) { is(value1) {
حسنا ، السجل
when(io.wb.wbWrite) { data_4 := Mux(io.wb.addr === ADDR_4, io.wb.dat_master, data_4) }
Mux[T <: Data](cond: Bool, con: T, alt: T): T
- معدد إرسال عادي
قمنا بتضمين شيء مشابه لعدادنا متعدد القنوات ، وقمنا بتعليق التسجيلات لإدارة القنوات وقبعة. ولكن هنا قريب من وحدة تحكم ناقل WB العالمي التي سننقل إليها بطاقة ذاكرة من هذا النوع:
val readMemMap = Map( ADDR_1 -> DATA_1, ADDR_2 -> DATA_2 ) val writeMemMap = Map( ADDR_1 -> DATA_1, ADDR_2 -> DATA_2 )
لمثل هذه المهمة ، ستساعدنا trait
- شيء مثل mixins في Sala. ستكون المهمة الرئيسية هي جعل readMemMap: [Int, Data]
تبدو مثل Seq( -> )
، وسيكون من الرائع أيضًا أن تتمكن من نقل العنوان الأساسي ومجموعة البيانات داخل بطاقة الذاكرة
val readMemMap = Map( ADDR_1_BASE -> DATA_SEQ, ADDR_2 -> DATA_2 )
ما سيتم توسيعه إلى شيء مشابه ، حيث WB_DAT_WIDTH هو عرض البيانات بالبايت
val readMemMap = Map( ADDR_1_BASE + 0 * (WB_DAT_WIDHT)-> DATA_SEQ_0, ADDR_1_BASE + 1 * (WB_DAT_WIDHT)-> DATA_SEQ_1, ADDR_1_BASE + 2 * (WB_DAT_WIDHT)-> DATA_SEQ_2, ADDR_1_BASE + 3 * (WB_DAT_WIDHT)-> DATA_SEQ_3 ADDR_2 -> DATA_2 )
لتنفيذ ذلك ، نكتب دالة محول من Map[Int, Any]
إلى Seq[(Bool, UInt)]
. عليك استخدام mathcing نمط سكالا.
def parseMemMap(memMap: Map[Int, Any]): Seq[(Bool, UInt)] = memMap.flatMap { case(addr, data) => data match { case a: UInt => Seq((io.wb.adr === addr.U) -> a) case a: Seq[UInt] => a.map(x => (io.wb.adr === (addr + io.wb.dat_slave.getWidth / 8).U) -> x) case _ => throw new Exception("WRONG MEM MAP!!!") } }.toSeq
أخيرًا ، ستبدو سماتنا كما يلي:
trait wishboneSlaveDriver { val io : wishboneSlave val readMemMap: Map[Int, Any] val writeMemMap: Map[Int, Any] val parsedReadMap: Seq[(Bool, UInt)] = parseMemMap(readMemMap) val parsedWriteMap: Seq[(Bool, UInt)] = parseMemMap(writeMemMap) val wb_ack = RegInit(false.B) val wb_dat = RegInit(0.U(io.wb.dat_slave.getWidth.W)) when(io.wb.wbTransaction) { wb_ack := true.B }.otherwise { wb_ack := false.B } when(io.wb.wbRead) { wb_dat := MuxCase(default = 0.U, parsedReadMap) } when(io.wb.wbWrite) { parsedWriteMap.foreach { case(addrMatched, data) => data := Mux(addrMatched, io.wb.dat_master, data) } } wb_dat <> io.wb.dat_slave wb_ack <> io.wb.ack_slave def parseMemMap(memMap: Map[Int, Any]): Seq[(Bool, UInt)] = { } }
قليلا عن سكالا:
io , readMemMap, writeMemMap
هي الحقول المجردة io , readMemMap, writeMemMap
'a ، والتي يجب تحديدها في الصف الذي سنقوم بخلطه فيه.
كيفية استخدامه
من أجل خلط trait
مع الوحدة ، يجب استيفاء العديد من الشروط:
io
يجب أن يرث من فئة wishboneSlave
- بحاجة إلى إعلان اثنين من بطاقات الذاكرة
readMemMap
و writeMemMap
class WishboneMultiChannelCounter extends Module { val BASE = 0x11A00000 val OUT = 0x00000100 val S_EN = 0x00000200 val H_EN = 0x00000300 val wbAddrWidth = 32 val wbDataWidth = 32 val wbTagWidth = 0 val width = Seq(32, 16, 8, 4) val io = IO(new wishboneSlave(wbAddrWidth, wbDataWidth, wbTagWidth) { val hardwareEnable: Vec[Bool] = Input(Vec(width.length, Bool())) }) val counter = Module(new MultiChannelCounter(width)) val softwareEnable = RegInit(0.U(width.length.W)) width.indices.foreach(i => counter.io.enable(i) := io.hardwareEnable(i) && softwareEnable(i)) val readMemMap = Map( BASE + OUT -> width.indices.map(counter.io.getOut), BASE + S_EN -> softwareEnable, BASE + H_EN -> io.hardwareEnable.asUInt ) val writeMemMap = Map( BASE + S_EN -> softwareEnable ) }
نقوم بإنشاء سجل softwareEnable
، تتم إضافته إلى "و" بواسطة إشارة إدخال hardwareEnable
ويذهب لتمكين counter[MultiChannelCounter]
.
نعلن عن بطاقتي ذاكرة للقراءة والكتابة: readMemMap
writeMemMap
، لمزيد من التفاصيل حول الهيكل ، انظر الفصل أعلاه.
في بطاقة ذاكرة القراءة ، نقوم بنقل قيمة العداد لكل قناة * ، softwareEnable
القابلة للتمكين hardwareEnable
القابلة hardwareEnable
. وللسجل نعطي فقط تسجيل softwareEnable
القابلة للتمكين.
* width.indices.map(counter.io.getOut)
- تصميم غريب ، سنقوم بتحليله في أجزاء.
width.indices
- سيعرض مصفوفة بمؤشرات العناصر ، أي إذا كان width.length == 4
ثم width.indices = {0, 1, 2, 3}
{0, 1, 2, 3}.map(counter.io.getOut)
- تعطي شيئًا مثل هذا:
{ counter.io.getOut(0), counter.io.getOut(1), /*...*/ }
الآن بالنسبة لأي وحدة على إزميل ، يمكننا الإعلان عن بطاقات الذاكرة للقراءة والكتابة وتوصيل جهاز التحكم في ناقل الترقوة العالمي عند التوليد ، شيء من هذا القبيل:
class wishbone_multicahnnel_counter extends WishboneMultiChannelCounter with wishboneSlaveDriver object countersDriver extends App { Driver.execute(Array("-td", "./src/generated"), () => new wishbone_multicahnnel_counter ) }
wishboneSlaveDriver
- هذا هو بالضبط مزيج السمات الذي وصفناه تحت المفسد.
بالطبع ، هذه النسخة من وحدة التحكم العالمية بعيدة عن أن تكون نهائية ، لكنها بالأحرى خام على العكس. هدفه الرئيسي هو إظهار أحد الأساليب الممكنة لتطوير rtl على الإزميل. مع كل قدرات scala ، يمكن أن تكون هذه الأساليب أكبر بكثير ، لذلك كل مطور لديه مجاله الخاص للإبداع. صحيح أنه لا يوجد مكان يمكن أن نستلهم منه بشكل خاص ، باستثناء:
- مكتبة أدوات الإزميل الأصلية ، والتي يمكن أن تبحث عنها في وراثة الوحدات والواجهات
- https://github.com/freechipsproject/rocket-chip - risc-v يتم تنفيذ النواة بالكامل على الإزميل ، بشرط أن تعرف السكالا جيدًا ، للمبتدئين الذين لا يملكون نصف لتر ، كما يقولون ، ستستغرق وقتًا طويلاً جدًا لفهمها. لا توجد وثائق رسمية عن الهيكل الداخلي للمشروع.
MultiClockDomain
ماذا لو أردنا التحكم يدويًا في الساعة وإعادة ضبط الإشارات في الإزميل. حتى وقت قريب ، لم يكن بالإمكان القيام بذلك ، ولكن مع أحد الإصدارات الأحدث ، withClock {}
الدعم مع withClock {}
، مع withReset {}
ومع withClockAndReset {}
. دعونا نلقي نظرة على مثال:
class DoubleClockModule extends Module { val io = IO(new Bundle { val clockB = Input(Clock()) val in = Input(Bool()) val out = Output(Bool()) val outB = Output(Bool()) }) val regClock = RegNext(io.in, false.B) regClock <> io.out val regClockB = withClock(io.clockB) { RegNext(io.in, false.B) } regClockB <> io.outB }
regClock
- سجل سيتم تسجيله بواسطة إشارة clock
القياسية وإعادة تعيينه بواسطة إعادة regClock
القياسيةregClockB
- تم تسجيل نفس التسجيل في الساعة ، حسب تخمينك ، بواسطة إشارة io.clockB
، ولكن سيتم استخدام إعادة io.clockB
القياسية.
إذا أردنا إزالة clock
القياسية وإعادة reset
الإشارات تمامًا ، فيمكننا استخدام الميزة التجريبية - RawModule
(وحدة بدون ساعة قياسية وإشارات إعادة تعيين ، سيتعين التحكم في الجميع يدويًا). مثال:
class MultiClockModule extends RawModule { val io = IO(new Bundle { val clockA = Input(Clock()) val clockB = Input(Clock()) val resetA = Input(Bool()) val resetB = Input(Bool()) val in = Input(Bool()) val outA = Output(Bool()) val outB = Output(Bool()) }) val regClockA = withClockAndReset(io.clockA, io.resetA) { RegNext(io.in, false.B) } regClockA <> io.outA val regClockB = withClockAndReset (io.clockB, io.resetB) { RegNext(io.in, false.B) } regClockB <> io.outB }
مكتبة Utils
لا تنتهي المكافآت الممتعة للإزميل عند هذا الحد. عمل منشئوها بجد وكتبوا مكتبة صغيرة ولكنها مفيدة جدًا من الواجهات والوحدات والوظائف الصغيرة. من الغريب أنه لا يوجد وصف للمكتبة على الويكي ، ولكن يمكنك رؤية رابط ورقة الغش الذي في البداية (هناك قسمان أخيران)
الواجهات:
DecoupledIO
هو واجهة جاهزة / صالحة تستخدم بشكل شائع.
DecoupledIO(UInt(32.W))
- سيحتوي على إشارات:
val ready = Input(Bool())
val valid = Output(Bool())
val data = Output(UInt(32.W))
ValidIO
- مثل ValidIO
فقط بدون ready
الوحدات:
Queue
- تعد وحدة FIFO المتزامنة أمرًا مفيدًا للغاية ، حيث تبدو الواجهة
val enq: DecoupledIO[T]
- DecoupledIO
مقلوب
val deq: DecoupledIO[T]
- DecoupledIO
العادية
val count: UInt
- مقدار البيانات في قائمة الانتظار- وحدة تأخر
Pipe
- تدرج العدد التاسع من شرائح التسجيل Arbiter
- حكم على واجهات DecoupledIO
، لديها العديد من الأنواع الفرعية تختلف في نوع التحكيم
val in: Vec[DecoupledIO[T]]
- مجموعة من واجهات الإدخال
val out: DecoupledIO[T]
val chosen: UInt
- يعرض القناة المختارة
بقدر ما يمكنك أن تفهم من المناقشة على github - في الخطط العالمية هناك امتداد كبير لهذه المكتبة من الوحدات: مثل FIFO غير المتزامن ، LSFSR ، مقسمات التردد ، قوالب PLL لـ FPGA ؛ واجهات مختلفة ؛ تحكم لهم وأكثر من ذلك بكثير.
إزميل io-teseters
يجب ذكر إمكانية الاختبار في الإزميل ، في الوقت الحالي هناك طريقتان لاختبار هذا:
peekPokeTesters
- اختبارات محاكاة بحتة تختبر منطق تصميمكhardwareIOTeseters
بالفعل أكثر إثارة للاهتمام منذ ذلك الحين مع هذا النهج ، ستحصل على مقعد ثابت تم إنشاؤه مع الاختبارات التي كتبتها على الإزميل ، وحتى إذا كان لديك آلة لف ، فسوف تحصل حتى على جدول زمني.
ولكن حتى الآن ، لم يتم الانتهاء من نهج الاختبار ، ولا تزال المناقشة مستمرة. في المستقبل ، على الأرجح ستظهر أداة عالمية ، للاختبار والاختبارات سيكون من الممكن أيضًا الكتابة على الإزميل. ولكن في الوقت الحالي ، يمكنك إلقاء نظرة على ما هو موجود بالفعل وكيفية استخدامه هنا .
مساوئ الإزميل
هذا لا يعني أن الإزميل أداة عالمية ، وأنه يجب على الجميع التبديل إليه. ربما ، مثل ، جميع المشاريع في مرحلة التطوير ، لها عيوبها ، والتي تجدر الإشارة إليها من أجل الاكتمال.
العيب الأول وربما الأهم هو عدم وجود تدفق غير متزامن. ثقيل بما فيه الكفاية ، ولكن يمكن حلها بعدة طرق ، وأحدها هي النصوص البرمجية في الجزء العلوي من verilog ، والتي تحول إعادة الضبط المتزامن إلى غير متزامن. هذا من السهل القيام به لأنه جميع الإنشاءات في verilog المتولدة always
موحدة تمامًا.
العيب الثاني ، وفقا للكثيرين ، هو عدم قراءة فيريلوج المولد ، ونتيجة لذلك ، مضاعفات التصحيح. ولكن دعنا نلقي نظرة على الرمز الذي تم إنشاؤه من المثال باستخدام عداد بسيط
ولدت فيريلوج `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif module SimpleCounter( input clock, input reset, input io_enable, output [7:0] io_out ); reg [7:0] counter; reg [31:0] _RAND_0; wire [8:0] _T_7; wire [7:0] _T_8; wire [7:0] _GEN_0; assign _T_7 = counter + 8'h1; assign _T_8 = _T_7[7:0]; assign _GEN_0 = io_enable ? _T_8 : counter; assign io_out = counter; `ifdef RANDOMIZE integer initvar; initial begin `ifndef verilator #0.002 begin end `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{$random}}; counter = _RAND_0[7:0]; `endif // RANDOMIZE_REG_INIT end `endif // RANDOMIZE always @(posedge clock) begin if (reset) begin counter <= 8'h0; end else begin if (io_enable) begin counter <= _T_8; end end end endmodule
للوهلة الأولى ، يمكن لـ verilog الناتج أن يبتعد ، حتى في تصميم متوسط الحجم ، ولكن دعونا نلقي نظرة.
- تعرف RANDOMIZE - (قد تكون مفيدة عند الاختبار باستخدام أجهزة اختبار الإزميل) - بشكل عام عديمة الفائدة ، لكنها لا تتدخل بشكل خاص
- كما نرى اسم موانئنا ، والسجل محفوظ
- _GEN_0 هو متغير عديم الفائدة بالنسبة لنا ، ولكنه ضروري لكي يقوم firrtl للمترجم بإنشاء verilog. نحن أيضا لا ننتبه إليها.
- لا يزال هناك _T_7 و _T_8 ، سيتم تقديم كل المنطق التوافقي في فيريلوج الذي تم إنشاؤه خطوة بخطوة في شكل المتغيرات _T.
الأهم من ذلك أن جميع المنافذ والسجلات والأسلاك اللازمة لتصحيح الأخطاء تحافظ على أسمائها من الإزميل. وإذا لم تنظر فقط إلى verilog ولكن أيضًا إلى الإزميل ، فسرعان ما ستسير عملية تصحيح الأخطاء كما هو الحال مع verilog النقي.
الخلاصة
في الحقائق الحديثة ، تطور تطوير RTL ، سواء كان أسيكيًا أو fpga خارج البيئة الأكاديمية ، لفترة طويلة من استخدام رمز فيريلوج خالص مكتوب بخط اليد فقط إلى نوع أو نوع آخر من نص الجيل ، سواء كان نصًا صغيرًا من tcl أو IDE كاملًا مع مجموعة من الميزات.
الإزميل ، بدوره ، هو التطور المنطقي للغات لتطوير واختبار المنطق الرقمي. افترض أنه في هذه المرحلة هو بعيد عن الكمال ، ولكنه قادر بالفعل على توفير الفرص التي يمكنك من خلالها تحمل عيوبه. , .