Untuk waktu yang lama saya bermimpi belajar bagaimana bekerja dengan FPGA, saya melihat lebih dekat. Kemudian dia membeli papan debug, menulis beberapa halo dunia dan meletakkan papan di dalam kotak, karena tidak jelas apa yang harus dilakukan dengan itu. Lalu idenya muncul: mari kita tulis generator video komposit untuk TV CRT kuno. Idenya, tentu saja, lucu, tetapi saya tidak benar-benar mengenal Verilog, dan saya masih harus mengingatnya, dan saya tidak benar-benar membutuhkan generator ini ... Dan baru-baru ini saya ingin melihat ke arah prosesor perangkat lunak RISC-V . Anda harus mulai di suatu tempat, dan kode Rocket Chip (ini adalah salah satu implementasi) ditulis dalam Pahat - ini adalah DSL untuk Scala. Lalu tiba-tiba saya ingat bahwa selama dua tahun saya telah mengembangkan secara profesional di Scala dan menyadari: saatnya telah tiba ...
Jadi, jika Anda ingin membaca cerita dari kehidupan pemotong kawat, multimeter digital, dan osiloskop yang telah terwujud dengan sendirinya, selamat datang di kucing.
Jadi apa yang akan ada di artikel ini? Di dalamnya, saya akan menjelaskan upaya saya untuk menghasilkan sinyal video PAL komposit (mengapa PAL? - Saya baru saja menemukan tutorial yang baik khususnya pada generasi PAL) di papan Mars rover 2 oleh nckma . Tentang RISC-V dalam artikel ini saya tidak akan mengatakan apa-apa. :)
Pertama, sedikit tentang Scala dan Pahat: Scala adalah bahasa yang berjalan di atas Java Virtual Machine dan secara transparan menggunakan perpustakaan Java yang ada (walaupun ada juga Scala.js dan Scala Native). Ketika saya pertama kali mulai mempelajarinya, saya merasa bahwa itu adalah hibrida yang sangat baik dari "plus" dan Haskell (namun, kolega tidak berbagi pendapat ini) - itu adalah sistem tipe canggih canggih dan bahasa ringkas, tetapi karena kebutuhan untuk menyeberang fungsionalisme dengan OOP, banyak sekali konstruksi bahasa di beberapa tempat membangkitkan ingatan akan C ++. Namun, jangan takut pada Scala - ini adalah bahasa yang sangat ringkas dan aman dengan sistem tipe yang kuat, di mana pada awalnya Anda hanya dapat menulis sebagai Java yang ditingkatkan. Dan juga, sejauh yang saya tahu, Scala awalnya dikembangkan sebagai bahasa untuk pembuatan Bahasa Spesifik Domain yang nyaman - ini adalah saat Anda menggambarkan, katakanlah, peralatan digital atau catatan dalam bahasa formal, dan bahasa ini terlihat cukup logis dari sudut pandang area subjeknya. Dan kemudian Anda tiba-tiba mengetahui bahwa itu adalah kode yang benar di Scala (well, atau Haskell) - orang-orang baik hati menulis perpustakaan dengan antarmuka yang nyaman. Pahat hanyalah perpustakaan untuk Scala, yang memungkinkan kita untuk menggambarkan logika digital pada DSL yang nyaman, dan kemudian menjalankan kode Scala yang dihasilkan dan menghasilkan kode pada Verilog (atau yang lain) yang dapat disalin ke proyek Quartus. Baik, atau segera jalankan unit-test standar scala-style, yang mereka sendiri akan mensimulasikan testbenches dan mengeluarkan laporan hasil.
Untuk berkenalan dengan sirkuit digital, saya sangat merekomendasikan buku ini (sudah dalam versi cetak Rusia). Kenyataannya, perkenalan saya yang sistematis dengan dunia FPGA hampir berakhir pada buku ini, jadi kritik konstruktif dalam komentar ini disambut baik (namun, saya ulangi, buku ini luar biasa: buku ini menceritakan dari dasar-dasar pembuatan kreasi prosesor yang sederhana. Dan ada gambar di sana;)). Nah, menurut Pahat ada tutorial resmi yang bagus .
Penafian: penulis tidak bertanggung jawab atas peralatan yang terbakar, dan jika Anda memutuskan untuk mengulangi percobaan, lebih baik untuk memeriksa level sinyal dengan osiloskop, membuat ulang bagian analog, dll. Dan secara umum - perhatikan tindakan pencegahan keselamatan. (Misalnya, dalam proses menulis artikel, saya menyadari bahwa kaki-kaki juga anggota badan, dan tidak ada yang menempel di baterai pemanas sentral, berpegang pada output papan ...) Omong-omong, infeksi ini juga menyebabkan gangguan pada TV di kamar sebelah. dalam proses debug ...
Penyiapan proyek
Kami akan menulis kode dalam IntelliJ Idea Community Edition , karena sistem build akan sbt , jadi buat direktori, masukkan .gitignore
, project/build.properties
, project/plugins.sbt
dari sini dan
build.sbt agak disederhanakan def scalacOptionsVersion(scalaVersion: String): Seq[String] = { Seq() ++ {
Sekarang buka di Ide dan minta untuk mengimpor proyek sbt - sementara sbt akan mengunduh dependensi yang diperlukan.
Modul pertama
PWM
Pertama, mari kita coba menulis PWM sederhana. Logika saya kira-kira sebagai berikut: untuk menghasilkan sinyal siklus tugas n / m, kami awalnya memasukkan 0 dalam register dan kami akan menambahkan n padanya setiap langkah. Ketika nilai register melebihi m, kurangi m dan berikan level tinggi untuk satu siklus clock. Sebenarnya, itu akan menjadi buggy jika n> m, tetapi kami akan menganggap ini sebagai perilaku tidak terbatas, yang diperlukan untuk mengoptimalkan kasus aktual yang digunakan.
Saya tidak akan menceritakan kembali seluruh panduan pemula - bunyinya dalam setengah jam, saya hanya akan mengatakan bahwa untuk menjelaskan modul, kita perlu mengimpor chisel3._
dan mewarisi dari Module
kelas abstrak. Ini abstrak karena kita perlu menggambarkan Bundle
dengan nama io
- ia akan memiliki seluruh antarmuka modul. Pada saat yang sama, input clock
dan reset
akan muncul secara implisit - Anda tidak perlu menggambarkannya secara terpisah. Inilah yang terjadi:
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) }
Perhatikan, kita memanggil metode .W
int reguler untuk mendapatkan lebar port, dan metode .asUInt(width.W)
yang biasanya kita panggil dengan integer literal! Bagaimana ini mungkin? - yah, di Smalltalk kita hanya akan mendefinisikan metode baru untuk kelas Integer (atau apa pun namanya), tetapi dalam JVM kita masih tidak memiliki seluruh objek - ada juga tipe primitif, dan Scala memahami ini (dan, di samping itu, ada kelas pihak ketiga yang tidak dapat kami modifikasi). Oleh karena itu, ada berbagai s implisit: dalam hal ini, Scala mungkin menemukan sesuatu seperti
implicit class BetterInt(n: Int) { def W: Width = ... }
dalam lingkup saat ini, jadi int biasa memiliki kekuatan super. Berikut adalah salah satu fitur yang membuat Scala lebih ringkas dan mudah untuk membuat DSL.
Tambahkan sejumput tes untuk ini. 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
adalah salah satu dari tiga penguji standar di Chisel. Ini memungkinkan Anda untuk mengatur nilai pada input DUT (perangkat yang diuji) dan memeriksa nilai pada output. Seperti yang dapat kita lihat, ScalaTest biasa digunakan untuk pengujian dan pengujian memakan ruang 5 kali implementasi itu sendiri, yang pada prinsipnya adalah normal untuk perangkat lunak. Namun, saya menduga bahwa pengembang peralatan "cast in silicon" yang berpengalaman hanya akan tersenyum dengan sejumlah tes mikroskopis. Luncurkan dan 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
Ya, perbaiki di PWM di baris io.pulse := nextValue > io.denominator
masuk >=
, mulai ulang tes - semuanya berfungsi! Saya khawatir bahwa pengembang peralatan digital yang berpengalaman ingin membunuh saya karena sikap remeh untuk mendesain (dan beberapa pengembang perangkat lunak akan dengan senang hati bergabung dengan mereka) ...
Generator pulsa
Kita juga akan membutuhkan generator yang akan memberikan pulsa sinkronisasi untuk "setengah frame". Mengapa semi? karena pertama garis aneh ditransmisikan, lalu yang genap (baik, atau sebaliknya, tapi sekarang kita tidak peduli tentang lemak).
import chisel3._ import chisel3.util._ class OneShotPulseGenerator(val lengths: Seq[Int], val initial: Boolean) extends Module {
Ketika sinyal reset
dihapus, sinyal ini memotret dengan pulsa persegi panjang dengan panjang interval antara switching yang ditentukan oleh parameter lengths
, setelah itu tetap selamanya di kondisi terakhir. Contoh ini menunjukkan penggunaan tabel nilai menggunakan VecInit
, serta cara untuk mendapatkan lebar register yang diperlukan: chisel3.util.log2Ceil(maxVal + 1).W
. Sejujurnya saya tidak ingat bagaimana hal itu dilakukan di Verilog, tetapi di Pahat, untuk membuat modul seperti parameter dengan vektor nilai, cukup memanggil konstruktor kelas dengan parameter yang diperlukan.
Anda mungkin bertanya: "Jika input clock
dan reset
dihasilkan secara implisit, lalu bagaimana kita akan" mengisi ulang "generator pulsa untuk setiap frame?" Pengembang pahat telah menyediakan untuk semuanya:
val module = Module( new MyModule() ) val moduleWithCustomReset = withReset(customReset) { Module( new MyModule() ) } val otherClockDomain = withClock(otherClock) { Module( new MyModule() ) }
Implementasi naif dari generator sinyal
Agar TV memahami kami setidaknya, entah bagaimana, Anda perlu mendukung "protokol" level trik rata-rata: ada tiga level sinyal penting:
- 1.0V - warna putih
- 0.3V - warna hitam
- 0V - level khusus
Mengapa saya memanggil 0V khusus? Karena dengan transisi yang mulus dari 0.3V ke 1.0V, kami dengan lancar beralih dari hitam ke putih, dan antara 0V dan 0.3V, sejauh yang saya mengerti, tidak ada level menengah dan 0V hanya digunakan untuk sinkronisasi. (Bahkan, itu bahkan tidak berubah dalam kisaran 0V - 1V, tetapi -0,3V - 0,7V, tetapi, semoga, masih ada kapasitor di input)
Seperti yang diajarkan oleh artikel yang luar biasa ini , sinyal PAL komposit terdiri dari aliran berulang 625 garis berulang: kebanyakan dari mereka adalah garis, pada kenyataannya, gambar (secara genap dan terpisah secara terpisah), beberapa digunakan untuk keperluan sinkronisasi (kami melakukan generator untuk mereka sinyal), beberapa tidak terlihat di layar. Mereka terlihat seperti ini (saya tidak akan membajak dan memberikan tautan ke aslinya):
Mari kita coba gambarkan antarmuka modul:
BWGenerator
akan mengatur pengaturan waktu, dll., Perlu mengetahui pada frekuensi apa ia bekerja:
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
akan menghitung level sinyal luminance, serta sinyal warna tambahan:
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)) })
Dalam modul PalGenerator
, PalGenerator
cukup PalGenerator
dua modul yang ditentukan:
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 }
Dan sekarang kita dengan sedih menggambar burung hantu pertama ... 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,
Pembuatan Kode Disintesis
Ini semua bagus, tapi kami ingin menjahit desain yang dihasilkan menjadi papan. Untuk melakukan ini, Anda perlu mensintesis Verilog. Ini dilakukan dengan cara yang sangat sederhana:
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)) } }
Sebenarnya, dalam metode dua-baris main()
kita melakukannya dua kali, sisa kode adalah modul lain yang menempel berikutnya
Generator gambar tes benar-benar membosankan 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) }
Sekarang Anda perlu memasukkannya ke dalam proyek Quartus. Untuk Mars rover 2, kita akan memerlukan versi gratis Quartus 13.1. Cara menginstalnya, ada tertulis di situs Mars rovers. Dari sana, saya mengunduh "Proyek Pertama" untuk papan Mars Rover 2, memasukkannya ke dalam repositori, dan sedikit memperbaikinya. Karena saya bukan insinyur elektronik (dan FPGA, saya sebenarnya lebih tertarik sebagai akselerator daripada sebagai kartu antarmuka)
seperti dalam lelucon itu ...Programmer duduk jauh di dalam debugging.
Fit son:
"Papa, mengapa matahari terbit di timur setiap hari dan duduk di barat?"
"Apakah kamu memeriksa itu?"
- Sudah diperiksa.
- Sudah diperiksa?
- Bagus
- Apakah itu berhasil?
- Berhasil.
- Apakah itu bekerja setiap hari?
- Ya setiap hari.
- Maka demi Tuhan, Nak, jangan menyentuh apa pun, jangan ubah apa pun.
... Saya baru saja menghapus generator sinyal VGA dan menambahkan modul saya.

Setelah itu, saya menghubungkan TV tuner analog ke komputer lain (laptop), sehingga setidaknya ada isolasi galvanis antara catu daya generator sinyal dan konsumen dan baru saja mengirim sinyal dari pin IO7 (+) dan GND (-) papan ke input komposit (minus ke kontak eksternal, plus ke tengah). Nah, itu, sebagai "sederhana" ... Itu hanya akan terjadi jika tangan tumbuh dari tempat seharusnya, atau jika saya memiliki kabel penghubung perempuan-laki-laki. Tapi saya hanya punya banyak kabel pria-pria. Tapi aku punya kegigihan dan gigitan! Secara umum, ada satu sembelit, saya memang membuat diri saya hampir menjadi pekerja - dengan kesulitan, tetapi berpegang teguh pada papan. Dan inilah yang saya lihat:

Sebenarnya, saya, tentu saja, sedikit menipu Anda. Kode yang ditunjukkan di atas saya dapatkan setelah sekitar tiga jam debug "pada perangkat keras", tetapi, sialnya, saya menulisnya, dan itu berhasil !!! Dan, mengingat bahwa saya hampir tidak terbiasa dengan elektronik yang serius, saya pikir tugas itu tidak buruk, tugas yang sulit.
Pembuatan video berwarna
Nah, kemudian, masalahnya tetap kecil - untuk menambahkan generator sinyal video warna. Saya mengambil tutorial dan mulai mencoba untuk membentuk warna meledak (ditambahkan ke tingkat hitam dari gelombang sinus pada frekuensi pembawa sinyal warna, yang diproduksi untuk waktu yang singkat selama HSync) dan, pada kenyataannya, sinyal warna sesuai dengan formula. Tapi itu tidak keluar, bahkan jika Anda retak ... Pada titik tertentu saya sadar bahwa, meskipun fakta bahwa frekuensi tidak menarik pandangan saya sekilas pada dokumen, TV hampir tidak disetel ke yang sewenang-wenang. Setelah mencari, saya menemukan bahwa PAL menggunakan frekuensi pembawa 4,43 MHz. Masalahnya adalah topinya, pikirku. "Brengsek," jawab sang tuner. Setelah seharian debugging dan hanya sekali melihat sekilas warna dalam gambar (apalagi, ketika saya mengatakan kepada tuner bahwa itu adalah NTSC pada umumnya)
... Saya menyadari betapa putus asa sebenarnya terlihat Kemudian saya menyadari bahwa saya tidak dapat melakukannya tanpa osiloskop. Dan, seperti yang telah saya katakan, saya tidak terbiasa dengan elektronik, dan, tentu saja, saya tidak memiliki keajaiban teknologi di rumah. Untuk membeli? Sedikit mahal untuk satu percobaan ... Dan dari apa itu bisa dibangun di atas lutut? Hubungkan sinyal ke input baris kartu suara? Ya, 4 setengah megahertz - tidak mungkin untuk memulai (setidaknya tanpa perubahan). Hmm, Mars rover memiliki ADC pada 20 MHz, tetapi mentransfer aliran mentah antarmuka serial ke komputer tidak cukup. Nah, di suatu tempat, Anda masih harus memproses sinyal untuk ditampilkan, dan sebenarnya akan ada jumlah bit informasi yang dapat diterima, tetapi itu juga mengacaukan dengan port serial, menulis program untuk komputer ... Lalu saya berpikir bahwa insinyur harus mengembangkan ada kegigihan dalam dirinya sendiri: ada imager warna idle, ada ADC ... Tapi gambar hitam dan putih adalah output stabil ... Nah, biarkan generator sinyal debug sendiri!
Penyimpangan liris (seperti yang mereka katakan, "Pendapat siswa tidak harus bertepatan dengan pendapat guru, akal sehat dan aksioma Peano"): Ketika saya menambahkan generasi warna dengan semua jenis penggandaan dan hal-hal rumit lainnya, Fmax merosot kuat untuk pengkondisi sinyal. Apa itu Fmax? Sejauh yang saya mengerti dari buku teks Harris & Harris, CAD untuk FPGA lebih suka ketika Verilog ditulis tidak dalam hal apa pun seperti dalam standar, tetapi "dengan konsep": misalnya, hasilnya harus berupa rangkaian sinkron - semacam web asiklik terarah dari logika kombinasional (penambahan, penggandaan , pembagian, operasi logis, ...), masing-masing terjebak dengan input dan output ke output dan input pemicu. Pemicu di tepi sinyal clock mengingat nilai inputnya untuk seluruh siklus clock berikutnya, level yang harus stabil beberapa waktu sebelum depan dan beberapa waktu sesudahnya (ini adalah dua konstanta waktu). Sinyal-sinyal dari output pemicu, pada gilirannya, setelah sinyal clock mulai menjalankannya ke output dari logika kombinasional (dan, oleh karena itu, input dari pemicu lainnya. Nah, dan output dari rangkaian mikro), yang juga ditandai oleh dua interval: waktu di mana tidak ada output belum akan memiliki waktu untuk mulai berubah, dan waktu setelah itu perubahan akan menjadi tenang (asalkan input telah berubah sekali). Berikut adalah frekuensi maksimum di mana logika kombinasional memastikan bahwa persyaratan pemicu terpenuhi - dan ini adalah Fmax. Ketika sirkuit antara dua jam harus memiliki waktu untuk menghitung lebih banyak, Fmax berkurang. Tentu saja, saya ingin frekuensinya lebih besar, tetapi jika tiba-tiba melonjak 10 kali (atau bahkan jumlah domain frekuensi dalam laporan CAD menurun) - periksa, mungkin Anda mengacaukan sesuatu di suatu tempat, dan sebagai hasilnya, CAD menemukan ekspresi konstan dan dengan senang hati menggunakannya untuk optimisasi.
Promosi Osiloskop
Tidak, bukan yang setelahnya ada putaran osiloskop dan beberapa bagian tambahan, tetapi bootstrap osiloskop seperti bootstrap kompiler, hanya untuk osiloskop.
Kami akan membuat osiloskop, atas perintah, merekam beberapa sampel sinyal input, setelah itu hanya menampilkan yang direkam. Karena dia perlu entah bagaimana memberikan perintah untuk merekam, dan setelah - menavigasi melalui itu, kita akan memerlukan beberapa pengontrol tombol - saya menulis tidak terlalu nyaman, tetapi cukup primitif, ini dia:
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()) })
SHOCK! SENSASI! Untuk membuatnya bekerja, Anda hanya perlu ... 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 .
Kesimpulan
Scala + Chisel β , Higher-kinded types. Scala- , Chisel , . . β !
: " -?" β ! ...