छेनी - (काफी नहीं) डिजिटल तर्क के विकास के लिए एक नया दृष्टिकोण


माइक्रोइलेक्ट्रॉनिक के विकास के साथ, आरटीएल डिजाइन अधिक से अधिक हो गए हैं। वेरिलॉग कोड की पुन: प्रयोज्य बहुत सारी असुविधा है, यहां तक ​​कि उत्पन्न, मैक्रोज़ और सिस्टम वेरिलॉग चिप्स के साथ भी। हालांकि, छेनी, rtl विकास के लिए ऑब्जेक्ट और कार्यात्मक प्रोग्रामिंग की पूरी शक्ति को लागू करना संभव बनाता है, जो एक लंबे समय से प्रतीक्षित कदम है जो एएसआईसी और एफपीजीए डेवलपर्स के फेफड़ों को ताजी हवा से भर सकता है।


यह लेख मुख्य कार्यक्षमता का संक्षिप्त विवरण देगा और उपयोग के कुछ उपयोगकर्ता मामलों पर विचार करेगा, हम इस भाषा की कमियों के बारे में भी बात करेंगे। भविष्य में, यदि विषय दिलचस्प है, तो हम लेख को अधिक विस्तृत ट्यूटोरियल में जारी रखते हैं।


सिस्टम की आवश्यकताएं


  • स्केला बेस लेवल
  • verilog और डिजिटल डिजाइन के निर्माण के मूल सिद्धांत।
  • छेनी प्रलेखन रखें

मैं सरल उदाहरणों का उपयोग करके छेनी की मूल बातें समझने की कोशिश करूंगा, लेकिन अगर कुछ स्पष्ट नहीं है, तो आप यहां देख सकते हैं।


स्कैला के लिए, यह धोखा शीट एक त्वरित गोता लगाने में मदद कर सकती है।


छेनी के लिए एक समान है।


पूर्ण लेख कोड (scala sbt प्रोजेक्ट के रूप में) यहाँ पाया जा सकता है


सरल काउंटर


जैसा कि नाम से ही स्पष्ट है, In कंस्ट्रक्टिंग हार्डवेयर इन ए स्काला एंबेडेड लैंग्वेज ’छेनी एक हार्डवेयर डिस्क्रिप्शन लैंग्वेज है जो स्कैला के ऊपर निर्मित होती है।


संक्षेप में सब कुछ कैसे काम करता है, इसके बारे में: छेनी पर rtl विवरण से एक हार्डवेयर ग्राफ बनाया गया है, जो बदले में, firrtl भाषा में एक मध्यवर्ती विवरण में बदल जाता है, और उसके बाद अंतर्निहित बैकएंड के बाद fttl verilog से दुभाषिया उत्पन्न होता है।


आइए एक साधारण काउंटर के दो कार्यान्वयन देखें।


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 - आरटीएल मॉड्यूल विवरण के लिए कंटेनर
  • Bundle छेनी में एक डेटा संरचना है, जिसका उपयोग मुख्य रूप से इंटरफेस को परिभाषित करने के लिए किया जाता है।
  • io - बंदरगाहों के निर्धारण के लिए परिवर्तनशील
  • Bool - डेटा प्रकार, सरल एकल-बिट संकेत
  • UInt(width: Width) - अहस्ताक्षरित पूर्णांक, निर्माणकर्ता इनपुट के रूप में संकेत की थोड़ी गहराई को स्वीकार करता है।
  • RegInit[T <: Data](init: T) एक रजिस्टर कंस्ट्रक्टर है, यह इनपुट पर रीसेट मान लेता है और इसका डेटा प्रकार समान होता है।
  • <> - यूनिवर्सल सिग्नल कनेक्शन ऑपरेटर
  • when(cond: => Bool) { /*...*/ } - if when(cond: => Bool) { /*...*/ } में एनालॉग

हम इस बारे में बात करेंगे कि कौन सा बरामदा थोड़ी देर बाद छेनी उत्पन्न करता है। अब जरा इन दोनों डिजाइनों की तुलना करें। जैसा कि आप देख सकते हैं, छेनी में clk और reset संकेतों का कोई उल्लेख नहीं है। तथ्य यह है कि छेनी इन संकेतों को डिफ़ॉल्ट रूप से मॉड्यूल में जोड़ता है। 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 के निर्माता के लिए इनपुट पैरामीटर, Seq[Int] प्रकार है - पूर्णांक तत्वों के साथ एक अनुक्रम।
  • Seq तत्वों के एक अच्छी तरह से परिभाषित अनुक्रम के साथ स्काला में संग्रह के प्रकारों में से एक है।
  • .map हर किसी के लिए संग्रह के लिए एक परिचित कार्य है, प्रत्येक तत्व पर एक ही ऑपरेशन के कारण एक संग्रह को दूसरे में परिवर्तित करने में सक्षम है, हमारे मामले में, पूर्णांक मूल्यों का एक क्रम इसी बिट गहराई के साथ SimpleCounter अनुक्रम में बदल जाता है।

छेनी के बारे में थोड़ा सा:


  • Vec[T <: Data](gen: T, n: Int): Vec[T] - छेनी डेटा प्रकार, सरणी का एक एनालॉग है।
  • Module[T <: BaseModule](bc: => T): T तात्कालिक मॉड्यूल के लिए आवश्यक आवरण विधि है।
  • util.Cat[T <: Bits](r: Seq[T]): UInt - util.Cat[T <: Bits](r: Seq[T]): UInt कार्य, एनालॉग {1'b1, 2'b01, 4'h0} verilog में

बंदरगाहों पर ध्यान दें:
enable - Vec[Bool] * में पहले से ही तैनात, मोटे तौर पर एक-बिट संकेतों की एक सरणी में, प्रत्येक चैनल के लिए एक, यह UInt(width.length.W) बनाने के लिए संभव था।
out - हमारे सभी चैनलों की चौड़ाई के योग तक।


चर counters हमारे काउंटरों की एक सरणी है। हम प्रत्येक काउंटर के enable सिग्नल को संबंधित इनपुट पोर्ट से कनेक्ट करते हैं, और सभी उपयोग किए out संकेतों को एक साथ util.Cat अंतर्निहित उपयोग का उपयोग करते हैं। फ़ंक्शन को आउटपुट में अग्रेषित करते हैं।


हम getOut(i: Int) फ़ंक्शन को भी नोट करते हैं - यह फ़ंक्शन getOut(i: Int) चैनल के लिए out सिग्नल में बिट्स की सीमा की गणना करता है और वापस करता है। यह इस तरह के एक काउंटर के साथ आगे के काम में बहुत उपयोगी होगा। कुछ इस तरह से लागू करने से काम नहीं चलेगा


* Vec को Vector साथ भ्रमित नहीं किया जाना चाहिए, पहला छेनी में डेटा की एक सरणी है, दूसरा स्कैला में एक संग्रह है।


आइए अब इस मॉड्यूल को वेरिलॉग पर लिखने की कोशिश करें, सुविधा के लिए, यहां तक ​​कि सिस्टमविरलॉग पर भी।


सोचने के बाद, मैं इस विकल्प पर आया (सबसे अधिक संभावना है कि यह एकमात्र सही और सबसे इष्टतम एक नहीं है, लेकिन आप हमेशा टिप्पणियों में अपने कार्यान्वयन का सुझाव दे सकते हैं)।


Verilog
 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 

यह पहले से ही बहुत अधिक प्रभावशाली लग रहा है। लेकिन क्या होगा, हम आगे बढ़ते हैं और रजिस्टर एक्सेस के साथ लोकप्रिय विशबोन इंटरफेस पर पेंच फंसाते हैं।


बंडल इंटरफेस


विशबोन एएमबीए एपीबी के समान एक छोटी बस है, जिसका उपयोग मुख्य रूप से ओपन सोर्स आईपी कोर के लिए किया जाता है।


विकी पर अधिक विवरण: 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(/*...*/)) या None , संकेतों को पैरामीटर करते समय उपयोगी।

ऐसा लगता है कि कुछ भी असामान्य नहीं है। विज़ार्ड द्वारा इंटरफ़ेस का वर्णन, कुछ संकेतों और विधियों के अपवाद के साथ:


tag_master और tag_slave , विशबोन प्रोटोकॉल में वैकल्पिक सामान्य-उद्देश्य संकेत हैं, हम उन्हें देखेंगे यदि gotTag पैरामीटर true


wbTransaction , wbWrite , wbRead - बस के साथ काम को आसान बनाने के लिए कार्य करता है।


cloneType - सभी प्रकार के 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 के रूप में सार्वभौमिक विशबोन बस नियंत्रक के कार्यान्वयन के बारे में बात करेंगे, यह विशबोनस्लेव बस के साथ किसी भी मॉड्यूल के लिए किसी प्रकार का मिक्सिन होगा, मॉड्यूल के लिए आपको बस एक मेमोरी कार्ड को परिभाषित करने और पीढ़ी के दौरान इसके लिए नियंत्रक नियंत्रक मिश्रण करने की आवश्यकता है।


कार्यान्वयन


उनके लिए जो अभी भी उत्साही हैं

चलो हैंडलर के कार्यान्वयन के लिए आगे बढ़ते हैं। यह सरल और तुरंत एकल लेनदेन का जवाब देगा, पते के पूल से बाहर गिरने के मामले में, शून्य पर लौटें।


आइए भागों में विश्लेषण करें:


  • प्रत्येक लेनदेन को स्वीकार के साथ जवाब देने की आवश्यकता है


     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)) // getWidth   when(io.wb.wbRead) { wb_dat := MuxCase(default = 0.U, Seq( (io.wb.addr === ADDR_1) -> data_1, (io.wb.addr === ADDR_3) -> data_2, (io.wb.addr === ADDR_3) -> data_2 )) } wb_dat <> io.wb.dat_slave 

    • MuxCase[T <: Data] (default: T, mapping: Seq[(Bool, T)]): T verilog में 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) { // ... } is(value2) { // ... } } 

खैर, रिकॉर्ड


  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 हमारी मदद करेगी - साला में मिश्रण जैसे कुछ। मुख्य कार्य readMemMap: [Int, Data] करना 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)] से एक कन्वर्टर फंक्शन लिखते हैं। आपको स्केला पैटर्न गणित का उपयोग करना होगा।


  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 हमारे trait के सार क्षेत्र हैं, जिन्हें उस वर्ग में परिभाषित किया जाना चाहिए जिसमें हम इसे io , readMemMap, writeMemMap

इसका उपयोग कैसे करें


मॉड्यूल के साथ हमारी 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 के काउंटर मूल्य को स्थानांतरित करते हैं। और रिकॉर्ड के लिए हम केवल 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 - यह वास्तव में ट्रिट मिक्स है जिसे हमने स्पॉइलर के तहत वर्णित किया है।


बेशक, सार्वभौमिक नियंत्रक का यह संस्करण अंतिम से बहुत दूर है, बल्कि इसके विपरीत है। इसका मुख्य लक्ष्य छेनी पर आरटीएल विकसित करने के संभावित तरीकों में से एक को प्रदर्शित करना है। स्काला की सभी क्षमताओं के साथ, ऐसे दृष्टिकोण बहुत बड़े हो सकते हैं, इसलिए प्रत्येक डेवलपर के पास रचनात्मकता के लिए अपना क्षेत्र है। सच है, विशेष रूप से, को छोड़कर कहीं से भी प्रेरित नहीं है:


  • देशी छेनी बर्तन पुस्तकालय, जिसके बारे में थोड़ा आगे, वहाँ आप मॉड्यूल और इंटरफेस की विरासत को देख सकते हैं
  • https://github.com/freechipsproject/rocket-chip - risc-v पूरे कर्नेल को छेनी पर लागू किया जाता है, बशर्ते कि आप स्कैला को अच्छी तरह से जानते हों, शुरुआती आधे लीटर के बिना, जैसा कि वे कहते हैं, आपको समझने में बहुत लंबा समय लगेगा। परियोजना की आंतरिक संरचना पर कोई आधिकारिक दस्तावेज नहीं है।

MultiClockDomain


क्या होगा अगर हम मैन्युअल रूप से घड़ी को नियंत्रित करना चाहते हैं और छेनी में संकेतों को रीसेट करें। हाल तक, यह नहीं किया जा सका, लेकिन नवीनतम रिलीज़ में से एक के साथ, समर्थन 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 संकेत द्वारा देखा जाएगा और मानक reset द्वारा रीसेट किया जाएगा
  • regClockB - एक ही रजिस्टर देखा जाता है, आपने यह अनुमान लगाया, io.clockB सिग्नल द्वारा, लेकिन मानक रीसेट का उपयोग किया जाएगा।

यदि हम मानक clock को हटाना चाहते 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 } 

लायब्रेरी


छेनी का सुखद बोनस वहाँ खत्म नहीं होता है। इसके रचनाकारों ने कड़ी मेहनत की और छोटे, इंटरफेस, मॉड्यूल, फ़ंक्शंस की एक छोटी लेकिन बहुत उपयोगी लाइब्रेरी लिखी। अजीब तरह से पर्याप्त है, विकि पर कोई पुस्तकालय विवरण नहीं है, लेकिन आप धोखा शीट लिंक देख सकते हैं, जिसमें बहुत शुरुआत में (दो अंतिम खंड हैं)


इंटरफेस:


  • DecoupledIO आमतौर पर तैयार / मान्य इंटरफ़ेस का उपयोग किया जाता है।
    DecoupledIO(UInt(32.W)) - इसमें सिग्नल शामिल होंगे:
    val ready = Input(Bool())
    val valid = Output(Bool())
    val data = Output(UInt(32.W))
  • ValidIO - केवल बिना ready किए ValidIO रूप में

मॉड्यूल:


  • Queue - तुल्यकालिक फीफो मॉड्यूल एक बहुत ही उपयोगी चीज है। इंटरफ़ेस जैसा दिखता है
    val enq: DecoupledIO[T] - उल्टा DecoupledIO
    val deq: DecoupledIO[T] - नियमित DecoupledIO
    val count: UInt - कतार में डेटा की मात्रा
  • Pipe - देरी मॉड्यूल, रजिस्टर स्लाइस की nth संख्या सम्मिलित करता है
  • Arbiter - DecoupledIO इंटरफेस पर Arbiter , मध्यस्थता के प्रकार में कई उप-प्रजातियाँ हैं
    val in: Vec[DecoupledIO[T]] - इनपुट इंटरफेस की सरणी
    val out: DecoupledIO[T]
    val chosen: UInt - चयनित चैनल दिखाता है

जहाँ तक आप गितुब पर चर्चा से समझ सकते हैं - वैश्विक योजनाओं में मॉड्यूल की इस लाइब्रेरी का एक महत्वपूर्ण विस्तार है: जैसे कि एसिंक्रोनस फीफो, एलएसएफएसआर, आवृत्ति डिवाइडर, एफपीजीए के लिए पीएलएल टेम्पलेट; विभिन्न इंटरफेस; उनके लिए नियंत्रकों और भी बहुत कुछ।


छेनी io-teseters


छेनी में परीक्षण की संभावना का उल्लेख किया जाना चाहिए, फिलहाल इसके परीक्षण के दो तरीके हैं:


  • peekPokeTesters - विशुद्ध रूप से सिमुलेशन परीक्षण जो आपके डिजाइन के तर्क का परीक्षण करते हैं
  • hardwareIOTeseters पहले से ही अधिक दिलचस्प है इस दृष्टिकोण के साथ, आपको एक परीक्षण के साथ जेनरेट किया गया टेस्सेट बेंच मिलेगा जो आपने छेनी पर लिखा था, और यहां तक ​​कि अगर आपके पास सत्यापनकर्ता है, तो भी आपको एक समयरेखा मिल जाएगी।


    लेकिन अभी तक, परीक्षण के दृष्टिकोण को अंतिम रूप नहीं दिया गया है, और चर्चा अभी भी जारी है। भविष्य में, सबसे अधिक संभावना एक सार्वभौमिक उपकरण दिखाई देगा, परीक्षण और परीक्षण के लिए छेनी पर लिखना भी संभव होगा। लेकिन अभी के लिए, आप देख सकते हैं कि पहले से ही क्या है और यहां इसका उपयोग कैसे किया जाए।



छेनी का नुकसान


यह कहना नहीं है कि छेनी एक सार्वभौमिक उपकरण है, और यह कि सभी को इसे स्विच करना चाहिए। वह, जैसे, शायद, सभी परियोजनाओं के विकास के स्तर पर, इसकी कमियां हैं, जो पूर्णता के लिए ध्यान देने योग्य हैं।


पहला और शायद सबसे महत्वपूर्ण दोष अतुल्यकालिक फ्लश की कमी है। वजनदार पर्याप्त है, लेकिन इसे कई तरीकों से हल किया जा सकता है, और उनमें से एक है बरामदे के ऊपर स्क्रिप्ट, जो सिंक्रोनस रीसेट को एसिंक्रोनस में बदल देता है। ऐसा करना आसान है always साथ उत्पन्न verilog में सभी निर्माण बहुत समान हैं।


दूसरा दोष, कई के अनुसार, उत्पन्न verilog की अपठनीयता है और, परिणामस्वरूप, डिबगिंग की जटिलता। लेकिन आइए एक सरल काउंटर के साथ उदाहरण से उत्पन्न कोड पर एक नज़र डालें


वर्िलोग उत्पन्न किया
 `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 

पहली नज़र में, उत्पन्न वेरिलॉग दूर, यहां तक ​​कि एक मध्यम आकार के डिजाइन में भी धक्का दे सकता है, लेकिन आइए एक नज़र डालें।


  • RANDOMIZE परिभाषित करता है - (छेनी-परीक्षक के साथ परीक्षण करते समय काम आ सकता है) - आम तौर पर बेकार हैं, लेकिन वे विशेष रूप से हस्तक्षेप नहीं करते हैं
  • जैसा कि हम अपने बंदरगाहों का नाम देखते हैं, और रजिस्टर संरक्षित है
  • _GEN_0 हमारे लिए एक बेकार चर है, लेकिन दुभाषिया के लिए आवश्यक है कि वेरिल उत्पन्न करें। हम भी इस पर ध्यान नहीं देते हैं।
  • _T_7 और _T_8 बने रहें, जनरेट किए गए वेरिलॉग में सभी संयोजन तर्क को चर _T के रूप में चरण दर चरण प्रस्तुत किया जाएगा।

सबसे महत्वपूर्ण बात, डिबगिंग के लिए आवश्यक सभी पोर्ट, रजिस्टर, तार अपने नाम को छेनी से रखते हैं। और अगर आप केवल वेरिलोग पर ही नहीं, बल्कि छेनी में भी देखते हैं, तो जल्द ही डिबगिंग की प्रक्रिया शुद्ध वर्मोग के साथ आसान हो जाएगी।


निष्कर्ष


आधुनिक वास्तविकताओं में, आरटीएल का विकास, चाहे वह अकादमिक वातावरण से बाहर हो या एफपीजी, लंबे समय तक केवल शुद्ध हस्तलिखित वेरिलॉग कोड का उपयोग करके एक या किसी अन्य प्रकार की स्क्रिप्ट पर चला गया है, यह एक छोटी सी स्क्रिप्ट या संपूर्ण ईटीई सुविधाओं का एक गुच्छा है।


डिजिटल तर्क के विकास और परीक्षण के लिए, चिसेल, बदले में, भाषाओं का तार्किक विकास है। मान लीजिए कि इस स्तर पर वह परिपूर्ण से बहुत दूर है, लेकिन पहले से ही ऐसे अवसर प्रदान करने में सक्षम है जिसके लिए आप उसकी कमियों को दूर कर सकते हैं। , .

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


All Articles