लंबे समय से मैंने एफपीजीए के साथ काम करने का तरीका सीखने का सपना देखा था, मैंने बारीकी से देखा। फिर उन्होंने एक डिबग बोर्ड खरीदा, हैलो दुनिया के एक जोड़े को लिखा और बोर्ड को एक बॉक्स में डाल दिया, क्योंकि यह स्पष्ट नहीं था कि इसके साथ क्या करना है। फिर विचार आया: आइए एक प्राचीन सीआरटी टीवी के लिए एक समग्र वीडियो जनरेटर लिखें। यह विचार, निश्चित रूप से मज़ेदार है, लेकिन मैं वास्तव में वेरिलॉग को नहीं जानता, और मुझे अभी भी इसे याद रखना है, और मुझे वास्तव में इस जनरेटर की आवश्यकता नहीं है ... और हाल ही में मैं RISC-V सॉफ़्टवेयर प्रोसेसर की ओर देखना चाहता था। आपको कहीं और शुरू करने की आवश्यकता है, और रॉकेट चिप कोड (यह कार्यान्वयन में से एक है) को चिसेल में लिखा गया है - यह स्काला के लिए ऐसा ही एक डीएसएल है। तब मुझे अचानक याद आया कि दो साल से मैं पेशेवर रूप से स्काला पर विकास कर रहा था और महसूस कर रहा था: समय आ गया था ...
तो, यदि आप वायर कटर, एक डिजिटल मल्टीमीटर और एक आस्टसीलस्कप के जीवन से एक कहानी पढ़ना चाहते हैं जो खुद को महसूस किया है, तो बिल्ली का स्वागत करें।
तो इस लेख में क्या होगा? इसमें, मैं एक समग्र PAL वीडियो सिग्नल उत्पन्न करने के अपने प्रयासों का वर्णन करूँगा (क्यों PAL? - मैं अभी एक अच्छा ट्यूटोरियल भर आया था विशेष रूप से PAL पीढ़ी पर) nverma द्वारा मंगल रोवर 2 बोर्ड पर। इस लेख में RISC-V के बारे में मैं कुछ भी नहीं कहूंगा। :)
सबसे पहले, स्काला और चिसेल के बारे में थोड़ा: स्काला एक भाषा है जो जावा वर्चुअल मशीन के शीर्ष पर चलती है और पारदर्शी रूप से मौजूदा जावा पुस्तकालयों का उपयोग करती है (हालाँकि इसमें स्काला.जैस और स्काला नेटिव भी हैं)। जब मैंने पहली बार इसका अध्ययन करना शुरू किया, तो मुझे यह महसूस हुआ कि यह "प्लसस" और हास्केल का बहुत व्यवहार्य संकर था (हालांकि, सहकर्मी इस राय को साझा नहीं करते हैं) - यह एक दर्दनाक उन्नत प्रकार की प्रणाली और संक्षिप्त भाषा है, लेकिन इसके साथ कार्यात्मकता को पार करने की आवश्यकता के कारण OOP की बहुतायत में कुछ स्थानों पर C ++ की यादों का निर्माण किया गया। हालांकि, स्काला से डरो मत - यह एक शक्तिशाली प्रकार की प्रणाली के साथ एक बहुत ही संक्षिप्त और सुरक्षित भाषा है, जिसमें सबसे पहले आप बस बेहतर जावा के रूप में लिख सकते हैं। और यह भी, जहां तक मुझे पता है, स्कैला को मूल रूप से डोमेन विशिष्ट भाषाओं के सुविधाजनक निर्माण के लिए एक भाषा के रूप में विकसित किया गया था - यह तब है जब आप औपचारिक भाषा में वर्णन, कहते हैं, डिजिटल उपकरण या नोट करते हैं, और यह भाषा अपने विषय क्षेत्र के दृष्टिकोण से काफी तार्किक लगती है। और फिर आपको अचानक पता चलता है कि यह स्काला (अच्छी तरह से, या हास्केल) में सही कोड था - बस उसी तरह के लोगों ने एक सुविधाजनक इंटरफ़ेस के साथ एक पुस्तकालय लिखा था। चिसेल स्कैला के लिए सिर्फ एक ऐसी लाइब्रेरी है, जो आपको एक सुविधाजनक डीएसएल पर डिजिटल लॉजिक का वर्णन करने की अनुमति देती है, और फिर परिणामी स्कैला कोड चलाती है और वेरिलोग (या कुछ और) पर कोड उत्पन्न करती है जिसे क्वार्टस प्रोजेक्ट में कॉपी किया जा सकता है। ठीक है, या तुरंत मानक स्काला-शैली इकाई-परीक्षण चलाएं, जो स्वयं टेस्टबेंच का अनुकरण करेंगे और परिणामों पर एक रिपोर्ट जारी करेंगे।
डिजिटल सर्किटरी से परिचित होने के लिए, मैं इस पुस्तक की सिफारिश करता हूं (यह पहले से ही मुद्रित रूसी संस्करण में है)। वास्तव में, FPGA की दुनिया के साथ मेरा व्यवस्थित परिचय इस पुस्तक पर लगभग समाप्त हो गया है, इसलिए टिप्पणियों में रचनात्मक आलोचना का स्वागत है (हालांकि, मैं दोहराता हूं, पुस्तक अद्भुत है: यह मूल बातें से एक साधारण वाहक प्रोसेसर के निर्माण के लिए बताती है। और वहां चित्र हैं;))। खैर, छेसेल के अनुसार एक अच्छा आधिकारिक ट्यूटोरियल है ।
डिस्क्लेमर: लेखक जले हुए उपकरण के लिए ज़िम्मेदार नहीं है, और यदि आप प्रयोग को दोहराने का निर्णय लेते हैं, तो सिग्नल के स्तर को आस्टसीलस्कप के साथ जांचना बेहतर है, एनालॉग भाग को रीमेक करना, आदि। और सामान्य तौर पर - सुरक्षा सावधानियों का पालन करें। (उदाहरण के लिए, लेख लिखने की प्रक्रिया में, मुझे एहसास हुआ कि पैर भी अंग हैं, और केंद्रीय हीटिंग बैटरी में उन्हें छड़ी करने के लिए कुछ भी नहीं है, बोर्ड के आउटपुट पर पकड़ ...) वैसे, इस संक्रमण ने अगले कमरे में टीवी में हस्तक्षेप का कारण बना। डिबगिंग के दौरान ...
प्रोजेक्ट सेटअप
हम IntelliJ Idea कम्युनिटी एडिशन में कोड लिखेंगे, क्योंकि बिल्ड सिस्टम sbt होगा, इसलिए एक डायरेक्टरी, put .gitignore
, project/build.properties
, project/plugins.sbt
यहां से बनाएं ।
कुछ सरलीकृत build.sbt def scalacOptionsVersion(scalaVersion: String): Seq[String] = { Seq() ++ {
अब इसे आइडिया में खोलें और sbt प्रोजेक्ट को आयात करने के लिए कहें - जबकि sbt आवश्यक निर्भरता को डाउनलोड करेगा।
पहला मॉड्यूल
PWM
सबसे पहले, आइए एक साधारण PWM लिखने का प्रयास करें। मेरा तर्क लगभग निम्नलिखित था: एक कर्तव्य चक्र संकेत n / m उत्पन्न करने के लिए, हमने शुरुआत में रजिस्टर में 0 डाला और हम इसे हर चरण में जोड़ देंगे। जब रजिस्टर का मान m से अधिक हो जाता है, तो m को घटाएं और एक घड़ी चक्र के लिए एक उच्च स्तर दें। दरअसल, यह n> m होने पर छोटी गाड़ी होगी, लेकिन हम इस पर एक अनिश्चित व्यवहार पर विचार करेंगे, जिसका उपयोग किए गए वास्तविक मामलों को अनुकूलित करने के लिए आवश्यक है।
मैं पूरे शुरुआती गाइड को रेट नहीं करूंगा - यह आधे घंटे में पढ़ता है, मैं केवल यह कहूंगा कि मॉड्यूल का वर्णन करने के लिए, हमें chisel3._
आयात करने की आवश्यकता है और अमूर्त वर्ग Module
से विरासत में मिला है। यह सार है क्योंकि हमें io
नाम के तहत Bundle
का वर्णन करने की आवश्यकता है - इसमें मॉड्यूल का संपूर्ण इंटरफ़ेस होगा। उसी समय, clock
और reset
इनपुट स्पष्ट रूप से दिखाई देंगे - आपको उन्हें अलग से वर्णन करने की आवश्यकता नहीं है। यहाँ क्या हुआ:
import chisel3._ class PWM(width: Int) extends Module { val io = IO(new Bundle { val numerator = Input(UInt(width.W)) val denominator = Input(UInt(width.W)) val pulse = Output(Bool()) }) private val counter = RegInit(0.asUInt(width.W)) private val nextValue = counter + io.numerator io.pulse := nextValue > io.denominator counter := Mux(io.pulse, nextValue - io.denominator, nextValue) }
ध्यान दें, हम .W
विधि .W
पोर्ट की चौड़ाई प्राप्त करने के लिए एक नियमित इंट पर कहते हैं, और .asUInt(width.W)
विधि जिसे हम आम तौर पर पूर्णांक शाब्दिक कहते हैं! यह कैसे संभव है? - ठीक है, स्मालटाक में हम सिर्फ इंटेगर क्लास के लिए एक नई विधि निर्धारित करेंगे (या इसे वहां जो भी कहते हैं), लेकिन जेवीएम में हमारे पास अभी भी पूरी वस्तु नहीं है - आदिम प्रकार भी हैं, और स्काला इसे समझता है (और इसके अलावा, तीसरे पक्ष के वर्ग हैं जिन्हें हम संशोधित नहीं कर सकते हैं)। इसलिए, विभिन्न प्रकार के निहितार्थ हैं: इस मामले में, स्काला शायद कुछ ऐसा पाता है
implicit class BetterInt(n: Int) { def W: Width = ... }
मौजूदा दायरे में, इसलिए साधारण किलों में सुपरपावर हैं। यहां ऐसी विशेषताओं में से एक है जो स्काला को डीएसएल बनाने के लिए अधिक संक्षिप्त और आसान बनाता है।
इसमें एक चुटकी टेस्ट मिलाएं। import chisel3.iotesters._ import org.scalatest.{FlatSpec, Matchers} object PWMSpec { class PWMTesterConstant(pwm: PWM, denum: Int, const: Boolean) extends PeekPokeTester(pwm) { poke(pwm.io.numerator, if (const) denum else 0) poke(pwm.io.denominator, denum) for (i <- 1 to 2 * denum) { step(1) expect(pwm.io.pulse, const) } } class PWMTesterExact(pwm: PWM, num: Int, ratio: Int) extends PeekPokeTester(pwm) { poke(pwm.io.numerator, num) poke(pwm.io.denominator, num * ratio) val delay = (1 to ratio + 2).takeWhile { _ => step(1) peek(pwm.io.pulse) == BigInt(0) } println(s"delay = $delay") for (i <- 1 to 10) { expect(pwm.io.pulse, true) for (j <- 1 to ratio - 1) { step(1) expect(pwm.io.pulse, false) } step(1) } } class PWMTesterApproximate(pwm: PWM, num: Int, denom: Int) extends PeekPokeTester(pwm){ poke(pwm.io.numerator, num) poke(pwm.io.denominator, denom) val count = (1 to 100 * denom).map { _ => step(1) peek(pwm.io.pulse).toInt }.sum val diff = count - 100 * num println(s"Difference = $diff") expect(Math.abs(diff) < 3, "Difference should be almost 0") } } class PWMSpec extends FlatSpec with Matchers { import PWMSpec._ behavior of "PWMSpec" def testWith(testerConstructor: PWM => PeekPokeTester[PWM]): Unit = { chisel3.iotesters.Driver(() => new PWM(4))(testerConstructor) shouldBe true } it should "return True constant for 1/1" in { testWith(new PWMTesterConstant(_, 1, true)) } it should "return True constant for 10/10" in { testWith(new PWMTesterConstant(_, 10, true)) } it should "return False constant for 1/1" in { testWith(new PWMTesterConstant(_, 1, false)) } it should "return False constant for 10/10" in { testWith(new PWMTesterConstant(_, 10, false)) } it should "return True exactly once in 3 steps for 1/3" in { testWith(new PWMTesterExact(_, 1, 3)) } it should "return good approximation for 3/10" in { testWith(new PWMTesterApproximate(_, 3, 10)) } }
PeekPokeTester
, Chisel में तीन मानक परीक्षकों में से एक है। यह आपको DUT के इनपुट पर मान सेट करने की अनुमति देता है (परीक्षण के तहत डिवाइस) और आउटपुट पर मान की जांच करता है। जैसा कि हम देख सकते हैं, सामान्य स्कैलेस्ट का उपयोग परीक्षणों के लिए किया जाता है और परीक्षण कार्यान्वयन के 5 गुना स्थान लेते हैं, जो कि, सिद्धांत रूप में, सॉफ्टवेयर के लिए सामान्य है। हालांकि, मुझे संदेह है कि "सिलिकॉन में डाली गई" उपकरण के अनुभवी डेवलपर्स केवल ऐसे सूक्ष्म संख्या परीक्षणों के साथ मुस्कुराएंगे। लॉन्च और उफ़ ...
Circuit state created [info] [0,000] SEED 1529827417539 [info] [0,000] EXPECT AT 1 io_pulse got 0 expected 1 FAIL ... [info] PWMSpec: [info] PWMSpec [info] - should return True constant for 1/1 [info] - should return True constant for 10/10 *** FAILED *** [info] false was not equal to true (PWMSpec.scala:56) [info] - should return False constant for 1/1 [info] - should return False constant for 10/10 [info] - should return True exactly once in 3 steps for 1/3 [info] - should return good approximation for 3/10
हाँ, इसे PWM में लाइन में ठीक करें io.pulse := nextValue > io.denominator
साइन ऑन >=
, परीक्षणों को पुनः आरंभ करें - सब कुछ काम करता है! मुझे डर है कि डिजिटल उपकरण के अनुभवी डेवलपर्स मुझे डिजाइन करने के लिए इस तरह के घृणित रवैये के लिए मारना चाहेंगे (और कुछ सॉफ्टवेयर डेवलपर्स ख़ुशी से उनके साथ जुड़ेंगे) ...
पल्स जनरेटर
हमें एक जनरेटर की भी आवश्यकता होगी जो "आधा फ्रेम" के लिए सिंक्रनाइज़ेशन के दालों को बाहर कर देगा। अर्ध क्यों? क्योंकि पहले विषम रेखाएँ संचारित होती हैं, फिर सम (अच्छी तरह से या इसके विपरीत, लेकिन अब हम वसा की परवाह नहीं करते हैं)।
import chisel3._ import chisel3.util._ class OneShotPulseGenerator(val lengths: Seq[Int], val initial: Boolean) extends Module {
जब reset
सिग्नल को हटा दिया जाता है, तो यह lengths
पैरामीटर द्वारा निर्दिष्ट स्विचिंग के बीच अंतराल की लंबाई के साथ आयताकार दालों के साथ शूट करता है, जिसके बाद यह अंतिम स्थिति में हमेशा के लिए रहता है। यह उदाहरण VecInit
का उपयोग करके मूल्य तालिकाओं के उपयोग को VecInit
, साथ ही आवश्यक रजिस्टर चौड़ाई प्राप्त करने का एक तरीका है: chisel3.util.log2Ceil(maxVal + 1).W
मुझे ईमानदारी से याद नहीं है कि यह वेरिलोग में कैसे किया गया था, लेकिन छेसेल में, एक वेक्टर मानों के द्वारा ऐसे मॉड्यूल को बनाने के लिए, यह आवश्यक पैरामीटर के साथ वर्ग निर्माता को कॉल करने के लिए पर्याप्त है।
आप शायद पूछते हैं: "अगर clock
और reset
इनपुट अंतर्निहित रूप से उत्पन्न होते हैं, तो हम प्रत्येक फ्रेम के लिए पल्स जनरेटर को" रिचार्ज "कैसे करेंगे?" छेनी डेवलपर्स ने सब कुछ के लिए प्रदान किया है:
val module = Module( new MyModule() ) val moduleWithCustomReset = withReset(customReset) { Module( new MyModule() ) } val otherClockDomain = withClock(otherClock) { Module( new MyModule() ) }
एक संकेत जनरेटर के Naive कार्यान्वयन
हमें कम से कम किसी तरह से समझने के लिए टीवी के लिए, आपको औसत चाल स्तर के "प्रोटोकॉल" का समर्थन करने की आवश्यकता है: तीन सिग्नल सिग्नल स्रोत हैं:
- 1.0V - सफेद रंग
- 0.3V - काला रंग
- 0V - विशेष स्तर
मैंने 0 वी को विशेष क्यों कहा? क्योंकि 0.3V से 1.0V तक एक चिकनी संक्रमण के साथ, हम आसानी से काले से सफेद में स्विच करते हैं, और 0V और 0.3V के बीच, जहां तक मैं समझ सकता हूं, कोई मध्यवर्ती स्तर नहीं हैं और 0V का उपयोग केवल सिंक्रनाइज़ेशन के लिए किया जाता है। (वास्तव में, यह 0V - 1V की सीमा में भी नहीं बदलता है, लेकिन -0.3V - 0.7V, लेकिन, उम्मीद है कि, इनपुट में एक संधारित्र अभी भी है)
जैसा कि यह अद्भुत लेख हमें सिखाता है, एक समग्र PAL सिग्नल में 625 लाइनों को दोहराने की एक अंतहीन धारा होती है: उनमें से अधिकांश लाइनें हैं, वास्तव में, चित्र (अलग-अलग और अलग-अलग विषम), कुछ का उपयोग सिंक्रनाइज़ेशन प्रयोजनों के लिए किया जाता है (हमने उनके लिए जनरेटर किया था। संकेत), कुछ स्क्रीन पर दिखाई नहीं दे रहे हैं। वे इस तरह दिखते हैं (मैं चोरी नहीं करूंगा और मूल को लिंक दूंगा):
आइए मॉड्यूल के इंटरफेस का वर्णन करने का प्रयास करें:
BWGenerator
समय आदि का प्रबंधन करेगा, उसे यह जानने की जरूरत है कि वह किस आवृत्ति पर काम करता है:
class BWGenerator(clocksPerUs: Int) extends Module { val io = IO(new Bundle { val L = Input(UInt(8.W)) val x = Output(UInt(10.W)) val y = Output(UInt(10.W)) val inScanLine = Output(Bool()) val millivolts = Output(UInt(12.W)) })
PalColorCalculator
ल्यूमिनेन्स सिग्नल के स्तर की गणना करेगा, साथ ही एक अतिरिक्त रंग संकेत:
class PalColorCalculator extends Module { val io = IO(new Bundle { val red = Input(UInt(8.W)) val green = Input(UInt(8.W)) val blue = Input(UInt(8.W)) val scanLine = Input(Bool()) val L = Output(UInt(8.W)) val millivolts = Output(UInt(12.W)) })
PalGenerator
मॉड्यूल में, PalGenerator
केवल दो निर्दिष्ट मॉड्यूल को PalGenerator
:
class PalGenerator(clocksPerUs: Int) extends Module { val io = IO(new Bundle { val red = Input(UInt(8.W)) val green = Input(UInt(8.W)) val blue = Input(UInt(8.W)) val x = Output(UInt(10.W)) val y = Output(UInt(10.W)) val millivolts = Output(UInt(12.W)) }) val bw = Module(new BWGenerator(clocksPerUs)) val color = Module(new PalColorCalculator) io.red <> color.io.red io.green <> color.io.green io.blue <> color.io.blue bw.io.L <> color.io.L bw.io.inScanLine <> color.io.scanLine bw.io.x <> io.x bw.io.y <> io.y io.millivolts := bw.io.millivolts + color.io.millivolts }
और अब हम दुखी होकर पहला उल्लू बनाते हैं ... package io.github.atrosinenko.fpga.tv import chisel3._ import chisel3.core.withReset import io.github.atrosinenko.fpga.common.OneShotPulseGenerator object BWGenerator { val ScanLineHSyncStartUs = 4.0 val ScanLineHSyncEndUs = 12.0 val TotalScanLineLengthUs = 64.0 val VSyncStart = Seq( 2, 30, 2, 30,
सिंथेसाइज्ड कोड जेनरेशन
यह सब अच्छा है, लेकिन हम परिणामस्वरूप डिजाइन को एक बोर्ड में सीवे करना चाहते हैं। ऐसा करने के लिए, आपको वेरिलॉग को संश्लेषित करने की आवश्यकता है। यह बहुत ही सरल तरीके से किया जाता है:
import chisel3._ import io.github.atrosinenko.fpga.common.PWM object Codegen { class TestModule(mhz: Int) extends Module { val io = IO(new Bundle { val millivolts = Output(UInt(12.W)) }) val imageGenerator = Module(new TestColorImageGenerator(540, 400)) val encoder = Module(new PalGenerator(clocksPerUs = mhz)) imageGenerator.io.x <> encoder.io.x imageGenerator.io.y <> encoder.io.y imageGenerator.io.red <> encoder.io.red imageGenerator.io.green <> encoder.io.green imageGenerator.io.blue <> encoder.io.blue io.millivolts := encoder.io.millivolts override def desiredName: String = "CompositeSignalGenerator" } def main(args: Array[String]): Unit = { Driver.execute(args, () => new PWM(12)) Driver.execute(args, () => new TestModule(mhz = 32)) } }
दरअसल, दो-लाइन विधि main()
हम इसे दो बार करते हैं, बाकी कोड एक और मॉड्यूल है जो अगले चिपक जाता है
बिल्कुल सुस्त परीक्षण तस्वीर जनरेटर class TestColorImageGenerator(width: Int, height: Int) extends Module { val io = IO(new Bundle { val red = Output(UInt(8.W)) val green = Output(UInt(8.W)) val blue = Output(UInt(8.W)) val x = Input(UInt(10.W)) val y = Input(UInt(10.W)) }) io.red := Mux((io.x / 32.asUInt + io.y / 32.asUInt)(0), 200.asUInt, 0.asUInt) io.green := Mux((io.x / 32.asUInt + io.y / 32.asUInt)(0), 200.asUInt, 0.asUInt) io.blue := Mux((io.x / 32.asUInt + io.y / 32.asUInt)(0), 0.asUInt, 0.asUInt) }
अब आपको इसे क्वार्टस परियोजना में बदलने की जरूरत है। मार्स रोवर 2 के लिए, हमें क्वार्टस 13.1 के मुफ्त संस्करण की आवश्यकता होगी। इसे कैसे स्थापित किया जाए यह मंगल रोवर साइट पर लिखा गया है। वहां से, मैंने मार्स रोवर 2 बोर्ड के लिए "फर्स्ट प्रोजेक्ट" डाउनलोड किया, इसे रिपॉजिटरी में रखा, और इसे थोड़ा सही किया। चूंकि मैं एक इलेक्ट्रॉनिक्स इंजीनियर नहीं हूं (और FPGA मैं वास्तव में इंटरफ़ेस कार्ड की तुलना में त्वरक के रूप में अधिक दिलचस्पी रखता हूं), तब
उस मजाक के रूप में ...प्रोग्रामर डीबगिंग में गहरा बैठता है।
फिट बेटा:
"पापा, सूरज रोज पूरब में क्यों उठता है और पश्चिम में बैठता है?"
"क्या आपने जाँच की थी?"
- जांच की गई।
- अच्छी तरह से जाँच की?
- अच्छा है।
- क्या यह काम करता है?
- यह भी।
- क्या यह हर दिन काम करता है?
- हाँ, हर दिन।
- फिर भगवान के लिए, बेटे, कुछ भी मत छुओ, कुछ भी मत बदलो।
... मैंने सिर्फ वीजीए सिग्नल जनरेटर को हटा दिया और अपना मॉड्यूल जोड़ा।

उसके बाद, मैंने एनालॉग टीवी ट्यूनर को दूसरे कंप्यूटर (लैपटॉप) से जोड़ा, ताकि सिग्नल जनरेटर और उपभोक्ता की बिजली आपूर्ति के बीच कम से कम कुछ गैल्वेनिक अलगाव हो और बस IO7 (+) और जीएनडी (-) से बोर्ड के पिन को समग्र इनपुट (माइनस) में भेजा जाए। बाहरी संपर्क के लिए, केंद्र के लिए)। ठीक है, वह है, "सरल" के रूप में ... यह केवल तब होगा जब हाथ उस जगह से बढ़े जहां यह होना चाहिए, ठीक है, या अगर मेरे पास महिला-पुरुष कनेक्टिंग तार थे। लेकिन मेरे पास केवल पुरुष-पुरुष तारों का एक गुच्छा है। लेकिन मेरे पास तप और नमस्कार है! सामान्य तौर पर, कब्ज का एक तार होता है, मैंने खुद को लगभग दो श्रमिक बना लिया - कठिनाई के साथ, लेकिन बोर्ड से चिपके हुए। और यहाँ वही है जो मैंने देखा:

वास्तव में, मैंने, निश्चित रूप से, आपको थोड़ा धोखा दिया। ऊपर दिखाया गया कोड "हार्डवेयर पर" डीबग करने के लगभग तीन घंटों के बाद मुझे मिला, लेकिन, लानत है, मैंने इसे लिखा है, और यह काम करता है !!! और, यह देखते हुए कि मैं गंभीर इलेक्ट्रॉनिक्स के साथ लगभग अपरिचित रहा करता था, मुझे लगता है कि कार्य भयानक नहीं था, क्या मुश्किल काम था।
रंग वीडियो पीढ़ी
ठीक है, फिर, बात छोटे के लिए बनी हुई है - एक रंगीन वीडियो सिग्नल जनरेटर जोड़ने के लिए। मैंने ट्यूटोरियल लिया और एक रंग फटने का प्रयास करना शुरू कर दिया (रंग संकेत के वाहक आवृत्ति पर साइन की लहर के काले स्तर में जोड़ा गया, HSync के दौरान उत्पादित थोड़े समय के लिए) और, वास्तव में, सूत्र के अनुसार रंग संकेत। लेकिन यह नहीं निकलता है, भले ही आप दरार ... कुछ बिंदु पर यह मुझ पर dawned है, इस तथ्य के बावजूद कि आवृत्ति ने दस्तावेज़ पर त्वरित नज़र से मेरी नज़र नहीं पकड़ी थी, टीवी को शायद ही एक मनमाना था। खोज करने के बाद, मैंने पाया कि पाल 4.43 मेगाहर्ट्ज की वाहक आवृत्ति का उपयोग करता है। बात टोपी की है, मैंने सोचा। "तुम भाड़ में जाओ," ट्यूनर ने जवाब दिया। डिबगिंग के पूरे दिन के बाद और केवल एक बार तस्वीर में रंग की झलक देखने के लिए (इसके अलावा, जब मैंने ट्यूनर को बताया कि यह सामान्य रूप से एनटीएससी था)
... मुझे एहसास हुआ कि वास्तव में निराशा कैसी दिखती है तब मुझे महसूस हुआ कि मैं आस्टसीलस्कप के बिना नहीं कर सकता। और, जैसा कि मैंने पहले ही कहा है, मैं इलेक्ट्रॉनिक्स से परिचित नहीं हूं, और निश्चित रूप से, मुझे घर पर तकनीक का ऐसा कोई चमत्कार नहीं है। खरीदने के लिए? एक प्रयोग के लिए थोड़ा महंगा ... और यह घुटने पर क्या बनाया जा सकता है? साउंड कार्ड के लाइन इनपुट के लिए एक सिग्नल कनेक्ट करें? हाँ, 4 और एक आधा मेगाहर्ट्ज़ - शुरू होने की संभावना नहीं है (कम से कम परिवर्तन के बिना)। हम्म, मंगल रोवर में 20 मेगाहर्ट्ज पर एडीसी है, लेकिन धारावाहिक इंटरफ़ेस की कच्ची धारा को कंप्यूटर में स्थानांतरित करना पर्याप्त नहीं है। ठीक है, कहीं न कहीं, आपको अभी भी प्रदर्शन के लिए सिग्नल को संसाधित करना है, और वास्तव में सूचना बिट्स की काफी स्वीकार्य मात्रा होगी, लेकिन यह सीरियल पोर्ट के साथ गड़बड़ करने के लिए भी है, कंप्यूटर के लिए प्रोग्राम लिखें ... फिर मैंने सोचा कि इंजीनियर को विकसित करना चाहिए अपने आप में एक स्वस्थ तप है: एक निष्क्रिय रंग इमेजर है, एक एडीसी है ... लेकिन काले और सफेद छवि का उत्पादन बहुत ही अच्छा है ... खैर, संकेत जनरेटर को स्वयं डिबग करने दें!
गीतात्मक विषयांतर (जैसा कि वे कहते हैं, "छात्र की राय को शिक्षक की राय, सामान्य ज्ञान और पीनो की स्वयंसिद्धता से मेल नहीं खाती है"): जब मैंने सभी प्रकार के गुणन और अन्य जटिल चीजों के साथ रंग निर्माण को जोड़ा, तो Fmax ने सिग्नल कंडीशनर के लिए दृढ़ता से सामना किया। Fmax क्या है? जहां तक मुझे हैरिस एंड हैरिस पाठ्यपुस्तक से समझ में आया, एफपीजीए के लिए सीएडी तब पसंद करता है जब वेरिलॉग मानक के भीतर किसी भी मामले में नहीं लिखा जाता है, लेकिन "अवधारणाओं द्वारा": उदाहरण के लिए, परिणाम एक समकालिक सर्किट होना चाहिए - संयोजन तर्क से एक प्रकार का दिशात्मक चक्रीय वेब (इसके अलावा, गुणा) , विभाजन, तार्किक संचालन, ...), क्रमशः इसके इनपुट और आउटपुट के साथ अटक गए और ट्रिगर्स के आउटपुट और इनपुट के लिए। घड़ी संकेत के किनारे पर एक ट्रिगर पूरे अगले घड़ी चक्र के लिए इसके इनपुट के मूल्य को याद करता है, जिसका स्तर सामने से कुछ समय पहले और कुछ समय बाद (ये दो समय स्थिरांक हैं) स्थिर होना चाहिए। ट्रिगर्स के आउटपुट से सिग्नल, बदले में, क्लॉक सिग्नल कॉम्बिनेशन लॉजिक के आउटपुट के लिए अपना रन शुरू करने के बाद (और, इसलिए, अन्य ट्रिगर्स के इनपुट। खैर, और माइक्रोक्रेकिट के आउटपुट), जिसे दो अंतरालों की विशेषता भी है: वह समय जिसके दौरान कोई आउटपुट अभी तक नहीं आया है। परिवर्तन शुरू करने का समय होगा, और उसके बाद का समय शांत हो जाएगा (बशर्ते कि इनपुट एक बार बदल गया हो)। यहां अधिकतम आवृत्ति है जिस पर कॉम्बिनेशन लॉजिक यह सुनिश्चित करता है कि ट्रिगर्स की आवश्यकताएं पूरी हों - यह Fmax है। जब दो घड़ियों के बीच के सर्किट में अधिक गणना करने का समय होना चाहिए, तो Fmax कम हो जाता है। बेशक, मैं चाहता हूं कि आवृत्ति बड़ी हो, लेकिन अगर यह अचानक 10 गुना कूद गया (या सीएडी रिपोर्ट में आवृत्ति डोमेन की संख्या भी कम हो गई) - जांच करें, हो सकता है कि आपने कहीं गड़बड़ कर दी हो, और परिणामस्वरूप, सीएडी को एक निरंतर अभिव्यक्ति मिली। और खुशी से अनुकूलन के लिए इसका इस्तेमाल किया।
आस्टसीलस्कप प्रमोशन
नहीं, ऐसा नहीं है जिसके बाद आस्टसीलस्कप का एक मोड़ है और मुट्ठी भर अतिरिक्त हिस्से हैं, लेकिन ऑसीलोस्कोप बूटस्ट्रैपिंग कंपाइलर बूटस्ट्रैपिंग की तरह है, केवल ऑसिलोस्कोप के लिए।
हम एक आस्टसीलस्कप बनाएंगे, कमांड पर, इनपुट सिग्नल के कुछ नमूनों को रिकॉर्ड करेंगे, जिसके बाद केवल रिकॉर्ड किए गए को प्रदर्शित किया जाएगा। चूंकि उसे किसी तरह रिकॉर्ड करने के लिए एक कमांड देने की आवश्यकता होगी, और उसके बाद - इसके माध्यम से नेविगेट करें, हमें कुछ बटन नियंत्रकों की आवश्यकता होगी - मैंने लिखा है कि बहुत सुविधाजनक नहीं है, लेकिन काफी आदिम है, यहां यह है:
class SimpleButtonController( clickThreshold: Int, pressThreshold: Int, period: Int, pressedIsHigh: Boolean ) extends Module { val io = IO(new Bundle { val buttonInput = Input(Bool()) val click = Output(Bool()) val longPress = Output(Bool()) })
झटका! अनुभूति होती है! इसे काम करने के लिए, आपको बस ... private val cycleCounter = RegInit(0.asUInt(32.W)) private val pressedCounter = RegInit(0.asUInt(32.W)) io.click := false.B io.longPress := false.B when (cycleCounter === 0.asUInt) { when (pressedCounter >= pressThreshold.asUInt) { io.longPress := true.B }.elsewhen (pressedCounter >= clickThreshold.asUInt) { io.click := true.B } cycleCounter := period.asUInt pressedCounter := 0.asUInt } otherwise { cycleCounter := cycleCounter - 1.asUInt when (io.buttonInput === pressedIsHigh.B) { pressedCounter := pressedCounter + 1.asUInt } } }
:
class Oscilloscope( clocksPerUs: Int, inputWidth: Int, windowPixelWidth: Int, windowPixelHeight: Int ) extends Module { val io = IO(new Bundle { val signal = Input(UInt(inputWidth.W)) val visualOffset = Input(UInt(16.W)) val start = Input(Bool()) val x = Input(UInt(10.W)) val y = Input(UInt(10.W)) val output = Output(Bool()) }) private val mem = SyncReadMem(1 << 15, UInt(inputWidth.W)) private val physicalPixel = RegInit(0.asUInt(32.W)) when (io.start) { physicalPixel := 0.asUInt } when (physicalPixel < mem.length.asUInt) { mem.write(physicalPixel, io.signal) physicalPixel := physicalPixel + 1.asUInt } private val shiftedX = io.x + io.visualOffset private val currentValue = RegInit(0.asUInt(inputWidth.W)) currentValue := ((1 << inputWidth) - 1).asUInt - mem.read( Mux(shiftedX < mem.length.asUInt, shiftedX, (mem.length - 1).asUInt) ) when (io.x > windowPixelWidth.asUInt || io.y > windowPixelHeight.asUInt) {
— , :
class OscilloscopeController( visibleWidth: Int, createButtonController: () => SimpleButtonController ) extends Module { val io = IO(new Bundle { val button1 = Input(Bool()) val button2 = Input(Bool()) val visibleOffset = Output(UInt(16.W)) val start = Output(Bool()) val leds = Output(UInt(4.W)) }) val controller1 = Module(createButtonController()) val controller2 = Module(createButtonController()) controller1.io.buttonInput <> io.button1 controller2.io.buttonInput <> io.button2 private val offset = RegInit(0.asUInt(16.W)) private val leds = RegInit(0.asUInt(4.W)) io.start := false.B when (controller1.io.longPress && controller2.io.longPress) { offset := 0.asUInt io.start := true.B leds := leds + 1.asUInt }.elsewhen (controller1.io.click) { offset := offset + (visibleWidth / 10).asUInt }.elsewhen (controller2.io.click) { offset := offset - (visibleWidth / 10).asUInt }.elsewhen (controller1.io.longPress) { offset := offset + visibleWidth.asUInt }.elsewhen (controller2.io.longPress) { offset := offset - visibleWidth.asUInt } io.visibleOffset := offset io.leds := leds }
(, ), - : — , — , ( — ). — ! , Verilog ?..
- , FPGA:

— ( IO7, VGA_GREEN R-2R ) :

, — , , . PAL — "Picture At Last (-, !)"
GitHub .
निष्कर्ष
Scala + Chisel — , Higher-kinded types. Scala- , Chisel , . . — !
: " -?" — ! ...