Ich habe lange davon getrĂ€umt, den Umgang mit FPGA zu lernen, und habe genau hingeschaut. Dann kaufte er ein Debug-Board, schrieb ein paar Hallo-Welten und steckte das Board in eine Schachtel, weil nicht klar war, was er damit anfangen sollte. Dann kam die Idee: Schreiben wir einen Composite-Video-Generator fĂŒr einen alten CRT-Fernseher. Die Idee ist natĂŒrlich lustig, aber ich kenne Verilog nicht wirklich und muss mich noch daran erinnern, und ich brauche diesen Generator nicht wirklich ... Und kĂŒrzlich wollte ich mich mit RISC-V- Softwareprozessoren befassen. Sie mĂŒssen irgendwo anfangen, und der Rocket-Chip- Code (dies ist eine der Implementierungen) ist in Chisel geschrieben - dies ist eine solche DSL fĂŒr Scala. Dann erinnerte ich mich plötzlich daran, dass ich mich zwei Jahre lang professionell an der Scala entwickelt hatte und erkannte: Die Zeit war gekommen ...
Wenn Sie also eine Geschichte aus dem Leben von Drahtschneidern, einem Digitalmultimeter und einem Oszilloskop lesen möchten, das sich selbst verwirklicht hat, dann sind Sie bei cat willkommen.
Was wird in diesem Artikel stehen? Darin werde ich meine Versuche beschreiben, ein zusammengesetztes PAL-Videosignal (warum PAL? - Ich habe gerade ein gutes Tutorial speziell zur PAL-Generierung gefunden) auf dem Mars Rover 2- Board von nckma zu generieren . Ăber RISC-V in diesem Artikel werde ich ĂŒberhaupt nichts sagen. :) :)
ZunĂ€chst ein wenig zu Scala und Chisel: Scala ist eine Sprache, die auf der Java Virtual Machine ausgefĂŒhrt wird und vorhandene Java-Bibliotheken transparent verwendet (obwohl es auch Scala.js und Scala Native gibt). Als ich anfing, es zu studieren, hatte ich das GefĂŒhl, dass es eine sehr brauchbare Mischung aus âPluspunktenâ und Haskell ist (Kollegen teilen diese Meinung jedoch nicht) - es ist ein schmerzlich fortschrittliches Typensystem und eine prĂ€gnante Sprache, aber wegen der Notwendigkeit, den Funktionalismus zu kreuzen OOP eine FĂŒlle von Sprachkonstrukten an einigen Stellen weckte Erinnerungen an C ++. Haben Sie jedoch keine Angst vor Scala - es ist eine sehr prĂ€gnante und sichere Sprache mit einem leistungsstarken Typsystem, in dem Sie zunĂ€chst einfach als verbessertes Java schreiben können. Soweit ich weiĂ, wurde Scala ursprĂŒnglich als Sprache fĂŒr die bequeme Erstellung domĂ€nenspezifischer Sprachen entwickelt. In diesem Fall beschreiben Sie beispielsweise digitale GerĂ€te oder Notizen in einer formalen Sprache, und diese Sprache sieht aus Sicht des Themenbereichs recht logisch aus. Und dann stellt man plötzlich fest, dass es der richtige Code in Scala (na ja, oder Haskell) war - nur freundliche Leute haben eine Bibliothek mit einer praktischen OberflĂ€che geschrieben. Chisel ist eine solche Bibliothek fĂŒr Scala, mit der wir die digitale Logik in einem praktischen DSL beschreiben und dann den resultierenden Scala-Code ausfĂŒhren und Code auf Verilog (oder etwas anderem) generieren können, der in das Quartus-Projekt kopiert werden kann. FĂŒhren Sie sofort die Standard-Unit-Tests im Scala-Stil aus, die selbst Testbenches simulieren und einen Bericht ĂŒber die Ergebnisse erstellen.
Um mich mit digitalen Schaltkreisen vertraut zu machen, empfehle ich dieses Buch (es ist bereits in der gedruckten russischen Version). TatsĂ€chlich endet meine systematische Bekanntschaft mit der FPGA-Welt fast mit diesem Buch, so dass konstruktive Kritik in den Kommentaren willkommen ist (ich wiederhole jedoch, das Buch ist wunderbar: Es erzĂ€hlt von den Grundlagen bis zur Schaffung eines einfachen ĂŒbermittelten Prozessors. Und es gibt dort Bilder;)). Nun, laut Chisel gibt es ein gutes offizielles Tutorial .
Haftungsausschluss: Der Autor ist nicht fĂŒr die verbrannten GerĂ€te verantwortlich. Wenn Sie das Experiment wiederholen möchten, ist es besser, die Signalpegel mit einem Oszilloskop zu ĂŒberprĂŒfen, den analogen Teil neu zu erstellen usw. Und im Allgemeinen - Sicherheitsvorkehrungen beachten. (Als ich zum Beispiel den Artikel schrieb, stellte ich fest, dass die Beine auch GliedmaĂen sind und es nichts gibt, was sie in die Zentralheizungsbatterie stecken und am Ausgang der Platine festhalten könnte ...) Ăbrigens verursachte diese Infektion auch Störungen im Fernseher im Nebenzimmer im Zuge des Debuggens ...
Projekteinrichtung
Wir werden den Code in IntelliJ Idea Community Edition schreiben, sbt wird das Build-System sein, also erstellen Sie ein Verzeichnis, setzen Sie .gitignore
, project/build.properties
, project/plugins.sbt
von hier und
etwas vereinfacht build.sbt def scalacOptionsVersion(scalaVersion: String): Seq[String] = { Seq() ++ {
Ăffnen Sie es nun in der Idee und fordern Sie den Import des sbt-Projekts an - wĂ€hrend sbt die erforderlichen AbhĂ€ngigkeiten herunterlĂ€dt.
Erste Module
PWM
Versuchen wir zunĂ€chst, eine einfache PWM zu schreiben. Meine Logik war ungefĂ€hr die folgende: Um ein TastverhĂ€ltnis-Signal n / m zu erzeugen, setzen wir zunĂ€chst 0 in das Register und fĂŒgen es bei jedem Schritt n hinzu. Wenn der Wert des Registers m ĂŒberschreitet, subtrahieren Sie m und geben Sie fĂŒr einen Taktzyklus einen hohen Pegel aus. Eigentlich wird es fehlerhaft sein, wenn n> m ist, aber wir werden dies als unbestimmtes Verhalten betrachten, das erforderlich ist, um die tatsĂ€chlich verwendeten FĂ€lle zu optimieren.
Ich werde nicht den gesamten AnfÀngerleitfaden nacherzÀhlen - er liest in einer halben Stunde, ich werde nur sagen, dass wir zur Beschreibung des Moduls chisel3._
importieren und von der abstrakten Klasse Module
erben mĂŒssen. Es ist abstrakt, weil wir das Bundle
unter dem Namen io
- es wird die gesamte Schnittstelle des Moduls haben. Gleichzeitig werden die clock
und reset
EingĂ€nge implizit angezeigt - Sie mĂŒssen sie nicht separat beschreiben. Folgendes ist passiert:
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) }
Beachten Sie, dass wir die .W
Methode .W
einem regulÀren int aufrufen, um die .W
zu erhalten, und die .asUInt(width.W)
-Methode, die wir im Allgemeinen auf einem Integer-Literal aufrufen! Wie ist das möglich? - Nun, in Smalltalk wĂŒrden wir nur eine neue Methode fĂŒr die Integer-Klasse definieren (oder wie auch immer sie dort heiĂt), aber in der JVM haben wir immer noch nicht das gesamte Objekt - es gibt auch primitive Typen, und Scala versteht dies (und zusĂ€tzlich) Es gibt Klassen von Drittanbietern, die wir nicht Ă€ndern können. Daher gibt es eine Vielzahl von impliziten s: In diesem Fall findet Scala wahrscheinlich so etwas
implicit class BetterInt(n: Int) { def W: Width = ... }
im gegenwĂ€rtigen Umfang haben also gewöhnliche Ints SuperkrĂ€fte. Hier ist eine der Funktionen, die Scala ĂŒbersichtlicher und einfacher macht, DSL zu erstellen.
FĂŒgen Sie eine Prise Tests hinzu. 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
ist einer von drei Standardtestern in Chisel. Hier können Sie die Werte an den EingĂ€ngen des PrĂŒflings (PrĂŒfling) einstellen und die Werte an den AusgĂ€ngen ĂŒberprĂŒfen. Wie wir sehen können, wird der ĂŒbliche ScalaTest fĂŒr Tests verwendet und die Tests belegen das FĂŒnffache der Implementierung selbst, was im Prinzip fĂŒr Software normal ist. Ich vermute jedoch, dass erfahrene Entwickler von GerĂ€ten, die "in Silizium gegossen" sind, nur mit einer solchen mikroskopischen Anzahl von Tests lĂ€cheln werden. Launch und oops ...
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
Ja, beheben Sie es in PWM in der Zeile io.pulse := nextValue > io.denominator
anmelden >=
, starten Sie die Tests neu - alles funktioniert! Ich befĂŒrchte, dass erfahrene Entwickler digitaler GerĂ€te mich wegen einer so leichtfertigen Einstellung zum Design umbringen wollen (und einige Softwareentwickler werden sich ihnen gerne anschlieĂen) ...
Impulsgeber
Wir werden auch einen Generator benötigen, der Synchronisationsimpulse fĂŒr "Halbbilder" ausgibt. Warum halb? weil zuerst die ungeraden Linien ĂŒbertragen werden, dann die geraden (na ja oder umgekehrt, aber jetzt ist uns Fett egal).
import chisel3._ import chisel3.util._ class OneShotPulseGenerator(val lengths: Seq[Int], val initial: Boolean) extends Module {
Wenn das reset
entfernt wird, schieĂt es mit Rechteckimpulsen mit den LĂ€ngen der Intervalle zwischen den SchaltvorgĂ€ngen, die durch den lengths
angegeben werden. lengths
bleibt es fĂŒr immer im letzten Zustand. Dieses Beispiel zeigt die Verwendung von Wertetabellen mit VecInit
sowie eine Möglichkeit, die erforderliche Registerbreite chisel3.util.log2Ceil(maxVal + 1).W
: chisel3.util.log2Ceil(maxVal + 1).W
. Ich kann mich ehrlich gesagt nicht erinnern, wie es in Verilog gemacht wurde, aber in Chisel reicht es aus, den Konstruktor der Klasse mit dem erforderlichen Parameter aufzurufen, um ein solches Modul zu erstellen, das durch einen Wertevektor parametrisiert wird.
Sie fragen wahrscheinlich: "Wenn die clock
und reset
EingĂ€nge implizit generiert werden, wie werden wir dann den Impulsgenerator fĂŒr jeden Frame" aufladen "?" MeiĂelentwickler haben fĂŒr alles gesorgt:
val module = Module( new MyModule() ) val moduleWithCustomReset = withReset(customReset) { Module( new MyModule() ) } val otherClockDomain = withClock(otherClock) { Module( new MyModule() ) }
Naive Implementierung eines Signalgenerators
Damit der Fernseher uns zumindest irgendwie versteht, mĂŒssen Sie das âProtokollâ des durchschnittlichen Trickpegels unterstĂŒtzen: Es gibt drei wichtige Signalpegel:
- 1,0 V - weiĂe Farbe
- 0,3 V - schwarze Farbe
- 0V - Sonderpegel
Warum habe ich 0V Special angerufen? Denn mit einem reibungslosen Ăbergang von 0,3 V auf 1,0 V wechseln wir reibungslos von Schwarz nach WeiĂ und zwischen 0 V und 0,3 V, soweit ich verstehen kann, gibt es keine Zwischenpegel und 0 V wird nur fĂŒr die Synchronisation verwendet. (TatsĂ€chlich Ă€ndert es sich nicht einmal im Bereich von 0 V - 1 V, sondern von -0,3 V - 0,7 V, aber hoffentlich befindet sich noch ein Kondensator am Eingang.)
Wie dieser wunderbare Artikel lehrt, besteht ein zusammengesetztes PAL-Signal aus einem endlosen Strom sich wiederholender 625 Zeilen: Die meisten davon sind Zeilen, tatsĂ€chlich Bilder (getrennt gerade und getrennt ungerade), einige werden fĂŒr Synchronisationszwecke verwendet (wir haben den Generator fĂŒr sie erstellt Signale), einige sind auf dem Bildschirm nicht sichtbar. Sie sehen so aus (ich werde keine Piraterie betreiben und Links zum Original geben):
Versuchen wir, die Schnittstellen der Module zu beschreiben:
BWGenerator
verwaltet die Timings usw. und muss wissen, mit welcher HĂ€ufigkeit es funktioniert:
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
berechnet den Pegel des Luminanzsignals sowie ein zusÀtzliches Farbsignal:
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)) })
Im PalGenerator
Modul verbinden PalGenerator
einfach PalGenerator
beiden angegebenen Module erneut:
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 }
Und jetzt zeichnen wir leider die erste Eule ... 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,
Generierung von synthetisiertem Code
Das ist alles gut, aber wir wollen das resultierende Design in eine Tafel nĂ€hen. Dazu mĂŒssen Sie Verilog synthetisieren. Dies geschieht auf sehr einfache Weise:
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)) } }
TatsÀchlich machen wir es in der zweizeiligen Methode main()
zweimal, der Rest des Codes ist ein weiteres Modul, das als nÀchstes bleibt
Absolut langweiliger Testbildgenerator 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) }
Jetzt mĂŒssen Sie es in das Quartus-Projekt verschieben. FĂŒr Mars Rover 2 benötigen wir die kostenlose Version von Quartus 13.1. Wie man es installiert, steht auf der Seite der Marsrover. Von dort habe ich das "Erste Projekt" fĂŒr das Mars Rover 2-Board heruntergeladen, es in das Repository gestellt und ein wenig korrigiert. Da ich kein Elektronikingenieur bin (und FPGA interessiere ich mich eigentlich mehr fĂŒr Beschleuniger als fĂŒr Schnittstellenkarten)
wie in diesem Witz ...Der Programmierer sitzt tief im Debuggen.
Fit Sohn:
"Papa, warum geht die Sonne jeden Tag im Osten auf und sitzt im Westen?"
"Hast du das ĂŒberprĂŒft?"
- GeprĂŒft.
- Gut geprĂŒft?
- Gut.
- Funktioniert es?
- Es funktioniert.
- Funktioniert es jeden Tag?
- Ja, jeden Tag.
- Um Gottes willen, mein Sohn, berĂŒhre nichts, Ă€ndere nichts.
... Ich habe gerade den VGA-Signalgenerator gelöscht und mein Modul hinzugefĂŒgt.

Danach habe ich den analogen TV-Tuner an einen anderen Computer (Laptop) angeschlossen, so dass zwischen der Stromversorgung des Signalgenerators und des Verbrauchers zumindest eine gewisse galvanische Trennung bestand, und nur ein Signal von den Pins IO7 (+) und GND (-) der Karte an den Composite-Eingang (Minus) gesendet zum externen Kontakt sowie zur Mitte). Nun, das heiĂt, als "einfach" ... Es wĂ€re einfach, wenn die HĂ€nde von dort wachsen wĂŒrden, wo sie sollten, na ja, oder wenn ich weiblich-mĂ€nnliche VerbindungsdrĂ€hte hĂ€tte. Aber ich habe nur ein paar mĂ€nnlich-mĂ€nnliche DrĂ€hte. Aber ich habe HartnĂ€ckigkeit und Zangen! Im Allgemeinen gibt es einen Draht der Verstopfung, ich habe mir zwei beinahe Arbeiter gemacht - mit Schwierigkeiten, aber am Brett festhaltend. Und hier ist was ich gesehen habe:

TatsĂ€chlich habe ich dich natĂŒrlich ein wenig getĂ€uscht. Den oben gezeigten Code habe ich nach ungefĂ€hr drei Stunden Debuggen "auf Hardware" erhalten, aber verdammt, ich habe ihn geschrieben und er funktioniert !!! Und da ich mit seriöser Elektronik frĂŒher fast nicht vertraut war, denke ich, dass die Aufgabe nicht schrecklich war, was fĂŒr eine schwierige Aufgabe.
Erzeugung von Farbvideos
Nun, dann bleibt die Sache fĂŒr kleine - einen Farbvideosignalgenerator hinzuzufĂŒgen. Ich nahm das Tutorial und begann zu versuchen, einen Farbburst (der fĂŒr kurze Zeit wĂ€hrend der HSync-Erzeugung zum Schwarzpegel der Sinuswelle bei der TrĂ€gerfrequenz des Farbsignals addiert wird) und tatsĂ€chlich das Farbsignal gemÀà der Formel zu bilden. Aber es kommt nicht heraus, selbst wenn Sie knacken ... Irgendwann wurde mir klar, dass der Fernseher trotz der Tatsache, dass mir die Frequenz bei einem kurzen Blick auf das Dokument nicht auffiel, kaum auf eine willkĂŒrliche Einstellung eingestellt war. Nach der Suche stellte ich fest, dass der PAL eine TrĂ€gerfrequenz von 4,43 MHz verwendet. Das Ding ist der Hut, dachte ich. "Fick dich", antwortete der Tuner. Nach einem ganzen Tag des Debuggens und nur einmalem Erkennen von Farben auf dem Bild (auĂerdem, als ich dem Tuner sagte, dass es im Allgemeinen NTSC war)
... Mir wurde klar, wie Hoffnungslosigkeit wirklich aussieht Dann wurde mir klar, dass ich ohne ein Oszilloskop nicht auskommen kann. Und wie ich bereits sagte, bin ich mit Elektronik nicht vertraut, und natĂŒrlich habe ich zu Hause kein solches Wunder der Technologie. Zu kaufen? Ein bisschen teuer fĂŒr ein Experiment ... Und woraus kann es auf dem Knie aufgebaut werden? Ein Signal an den Line-Eingang der Soundkarte anschlieĂen? Ja, viereinhalb Megahertz - es ist unwahrscheinlich, dass er startet (zumindest ohne Ănderung). Hmm, der Mars Rover hat einen ADC bei 20 MHz, aber es reicht nicht aus, einen Rohstrom der Geschwindigkeit der seriellen Schnittstelle auf den Computer zu ĂŒbertragen. Nun, irgendwo mĂŒssen Sie das Signal fĂŒr die Anzeige noch verarbeiten, und tatsĂ€chlich wird es eine akzeptable Menge an Informationsbits geben, aber es geht auch darum, mit der seriellen Schnittstelle herumzuspielen, Programme fĂŒr den Computer zu schreiben ... Dann dachte ich, dass der Ingenieur entwickeln sollte Es gibt eine gesunde HartnĂ€ckigkeit an sich: Es gibt einen Farb-Imager im Leerlauf, einen ADC ... Aber das SchwarzweiĂbild wird stabil ausgegeben ... Nun, lassen Sie den Signalgenerator sich selbst debuggen!
Lyrischer Exkurs (wie sie sagen: âDie Meinung des SchĂŒlers muss nicht mit der Meinung des Lehrers, des gesunden Menschenverstandes und der Axiomatik von Peano ĂŒbereinstimmenâ): Als ich die Farbgenerierung mit allen Arten von Multiplikationen und anderen komplizierten Dingen hinzufĂŒgte, sackte Fmax fĂŒr den Signalaufbereiter stark zusammen. Was ist Fmax? Soweit ich aus dem Harris & Harris-Lehrbuch verstanden habe, bevorzugt CAD fĂŒr FPGA, wenn Verilog nicht wie im Standard geschrieben wird, sondern "nach Konzepten": Das Ergebnis sollte beispielsweise eine Synchronschaltung sein - eine Art direktionales azyklisches Netz aus kombinatorischer Logik (Addition, Multiplikation) , Division, logische Operationen, ...), die mit ihren Ein- und AusgĂ€ngen an den AusgĂ€ngen bzw. EingĂ€ngen von Triggern hĂ€ngen bleiben. Ein Trigger an der Flanke des Taktsignals speichert den Wert seines Eingangs fĂŒr den gesamten nĂ€chsten Taktzyklus, dessen Pegel einige Zeit vor der Front und einige Zeit danach stabil sein muss (dies sind zwei Zeitkonstanten). Die Signale von den AusgĂ€ngen der Trigger laufen wiederum nach dem Beginn des Taktsignals zu den AusgĂ€ngen der Kombinationslogik (und damit zu den EingĂ€ngen anderer Trigger. Nun, und den AusgĂ€ngen der Mikroschaltung), die ebenfalls durch zwei Intervalle gekennzeichnet sind: die Zeit, in der noch kein Ausgang vorhanden ist Sie haben Zeit, sich zu Ă€ndern, und die Zeit, nach der sich die Ănderungen beruhigen (vorausgesetzt, die Eingabe hat sich einmal geĂ€ndert). Hier ist die maximale Frequenz, mit der die Kombinationslogik sicherstellt, dass die Anforderungen der Trigger erfĂŒllt werden - und dies ist Fmax. Wenn die Schaltung zwischen zwei Takten Zeit haben sollte, mehr zu zĂ€hlen, nimmt Fmax ab. NatĂŒrlich möchte ich, dass die Frequenz gröĂer ist, aber wenn sie plötzlich zehnmal sprang (oder sogar die Anzahl der Frequenzbereiche im CAD-Bericht abnahm) - ĂŒberprĂŒfen Sie, ob Sie irgendwo etwas durcheinander gebracht haben, und als Ergebnis fand CAD einen konstanten Ausdruck und gerne zur Optimierung genutzt.
Oszilloskop-Förderung
Nein, nicht der, nach dem das Oszilloskop und eine Handvoll zusĂ€tzlicher Teile gedreht werden, aber das Oszilloskop-Bootstrapping ist wie das Compiler-Bootstrapping, nur fĂŒr das Oszilloskop.
Wir werden auf Befehl ein Oszilloskop herstellen, das einige Abtastwerte des Eingangssignals aufzeichnet und danach nur das aufgezeichnete anzeigt. Da er irgendwie einen Befehl zum Aufzeichnen geben muss und danach - durch ihn navigieren, werden wir einige Tastencontroller benötigen - schrieb ich nicht sehr praktisch, aber ziemlich primitiv, hier ist es:
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()) })
SCHOCK! SENSATION! Damit es funktioniert, mĂŒssen Sie nur ... 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 .
Schlussfolgerungen
Scala + Chisel â , Higher-kinded types. Scala- , Chisel , . . â !
: " -?" â ! ...