业余笔记,或《 Scala FPGA开发人员如何配置的故事》

很长时间以来,我一直梦想着学习如何使用FPGA,因此我一直在仔细研究。 然后,他买了一个调试板,写了几个问候,并将该板放入盒子中,因为尚不清楚该如何处理。 然后这个想法就来了:让我们为古老的CRT电视编写一个复合视频生成器。 这个想法当然很有趣,但是我并不真正了解Verilog,我仍然必须记住它,并且我真的不需要这个生成器...最近,我想研究RISC-V软件处理器。 您需要从某个地方开始,然后用Chisel编写Rocket Chip代码(这是实现之一)-这是用于Scala的DSL。 然后我突然想起了我两年来一直在Scala上进行专业开发并意识到:时间到了...


因此,如果您想阅读剪线钳,数字万用表和示波器等已实现的故事,那么欢迎您。


那么这篇文章将会是什么呢? 在其中,我将描述由nckma火星探测器2板上生成复合PAL视频信号的尝试(为什么要使用PAL?-我刚刚遇到了一个专门介绍PAL生成的很好的教程)。 关于本文中的RISC-V,我什么也不会说。 :)


首先,介绍一下Scala和Chisel:Scala是一种在Java虚拟机之上运行的语言,它透明地使用现有的Java库(尽管也有Scala.js和Scala Native)。 当我刚开始研究它时,我感到它是“ pluss”和Haskell的非常可行的混合体(但是,同事们对此看法不一) –这是一个痛苦的高级类型系统和简洁的语言,但是由于需要交叉使用功能主义OOP在某些地方丰富的语言构造让人联想起C ++。 但是,不要害怕Scala-它是一种非常简洁和安全的语言,具有强大的类型系统,起初您可以简单地将其编写为改进的Java。 而且据我所知,Scala最初是为方便创建领域特定语言而开发的一种语言-这是当您以一种正式语言描述例如数字设备或音符时,从其主题领域的角度来看,这种语言看起来很合逻辑。 然后,您突然发现这是Scala(或者Haskell)中正确的代码-恰好人们写了一个具有方便接口的库。 Chisel就是这样的Scala库,它允许您在便捷的DSL上描述数字逻辑,然后运行生成的Scala代码并在Verilog(或其他方式)上生成代码,并将其复制到Quartus项目中。 好吧,或者立即运行标准的Scala风格的单元测试,它们自己将模拟测试平台并发布结果报告。


为了熟悉数字电路,我强烈推荐这本书 (它已经印在俄语版本中)。 实际上,我对FPGA领域的系统了解几乎以这本书结尾,因此欢迎在评论中进行建设性的批评(但是,我再说一遍,这本书很棒:它从基础到创建一个简单的传送式处理器都在讲述,那里有图片;)。) 好吧,据Chisel所说,有一个很好的官方教程


免责声明:作者对烧毁的设备不承担任何责任,如果您决定重复实验,最好用示波器检查信号电平,重新制作模拟部分,等等。 通常,请遵守安全预防措施。 (例如,在撰写本文的过程中,我意识到腿也是四肢,没有任何东西可以将它们粘在中央加热电池中,从而保持住板子的输出...)顺便说一句,这种感染还对隔壁房间的电视造成了干扰。在调试过程中...


项目设置


我们将在IntelliJ Idea Community Edition中编写代码,而sbt将是构建系统,因此创建一个目录, 从此处开始.gitignoreproject/build.propertiesproject/plugins.sbt


稍微简化了build.sbt
 def scalacOptionsVersion(scalaVersion: String): Seq[String] = { Seq() ++ { // If we're building with Scala > 2.11, enable the compile option // switch to support our anonymous Bundle definitions: // https://github.com/scala/bug/issues/10047 CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq() case _ => Seq("-Xsource:2.11") } } } name := "chisel-example" version := "1.0.0" scalaVersion := "2.11.12" resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases") ) // Provide a managed dependency on X if -DXVersion="" is supplied on the command line. val defaultVersions = Map( "chisel3" -> "3.1.+", "chisel-iotesters" -> "1.2.+" ) libraryDependencies ++= (Seq("chisel3","chisel-iotesters").map { dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) }) scalacOptions ++= scalacOptionsVersion(scalaVersion.value) 

现在,在Idea中打开它,并要求导入sbt项目-sbt将下载必要的依赖项。


第一个模块


脉宽调制


首先,让我们尝试编写一个简单的PWM 。 我的逻辑大致如下:要生成占空比信号n / m,我们首先将0放入寄存器,然后在每一步中将n加到寄存器中。 当寄存器的值超过m时,减去m并在一个时钟周期内发出高电平。 实际上,如果n> m,这将是越野车,但我们将认为这是不确定的行为,这对于优化使用的实际案例是必需的。


我不会讲完整的初学者指南-它需要半个小时的阅读时间,我只会说,为了描述模块,我们需要导入chisel3._并从抽象类Module继承。 这是抽象的,因为我们需要用名称io来描述io它具有模块的整个接口。 同时, clockreset输入将隐式显示-您无需分别描述它们。 这是发生了什么:


 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常规int .W调用.W方法以获取端口宽度,而通常在整数常量上调用.asUInt(width.W)方法! 这怎么可能? -好吧,在Smalltalk中,我们将为Integer类(或在那里所谓的任何东西)定义一个新方法,但是在JVM中,我们仍然没有整个对象-还有原始类型,Scala理解这一点(此外,有一些我们无法修改的第三方类)。 因此,存在各种隐式s:在这种情况下,Scala可能会发现类似


 implicit class BetterInt(n: Int) { def W: Width = ... } 

在目前的范围内,普通的int具有超能力。 这是使Scala更加简洁和易于创建DSL的功能之一。


为此添加一些测试。
 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(被测设备)的输入端设置值,并检查输出端的值。 如我们所见,通常的ScalaTest用于测试,测试占用的空间是实现本身的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行中io.pulse := nextValue > io.denominator登录>= ,重新开始测试-一切正常! 恐怕经验丰富的数字设备开发人员会以这种轻率的设计态度杀死我(有些软件开发人员会很乐意加入其中)...


脉冲发生器


我们还将需要一个生成器,该生成器将为“半帧”发出同步脉冲。 为什么半? 因为首先传输奇数行,然后传输偶数行(嗯,反之亦然,但是现在我们对脂肪不感兴趣)。


 import chisel3._ import chisel3.util._ class OneShotPulseGenerator(val lengths: Seq[Int], val initial: Boolean) extends Module { // Add sentinel value here, so no output flip required after the last state private val delayVecValues = lengths.map(_ - 1) :+ 0 val io = IO(new Bundle { val signal = Output(Bool()) }) private val nextIndex = RegInit(1.asUInt( log2Ceil(delayVecValues.length + 1).W )) private val countdown = RegInit(delayVecValues.head.asUInt( log2Ceil(lengths.max + 1).W )) private val output = RegInit(initial.asBool) private val delaysVec = VecInit(delayVecValues.map(_.asUInt)) private val moveNext = countdown === 0.asUInt private val finished = nextIndex === delayVecValues.length.asUInt when (!finished) { when (moveNext) { countdown := delaysVec(nextIndex) nextIndex := nextIndex + 1.asUInt output := !output }.otherwise { countdown := countdown - 1.asUInt } } io.signal := output } 

移除reset信号后,它会以矩形脉冲发射,其长度由lengths参数指定, lengths切换之间的间隔时间lengths ,此后它将永远保持在最后状态。 本示例演示了使用VecInit值表的使用以及获得所需寄存器宽度的方法: chisel3.util.log2Ceil(maxVal + 1).W 。 老实说,我不记得它是如何在Verilog中完成的,但是在Chisel中,要创建一个由值向量参数化的模块,只需用必需的参数调用类的构造函数即可。


您可能会问:“如果clockreset输入是隐式生成的,那么我们将如何为“每一帧的脉冲发生器”充电? 凿子开发人员提供了一切:


  val module = Module( new MyModule() ) val moduleWithCustomReset = withReset(customReset) { Module( new MyModule() ) } val otherClockDomain = withClock(otherClock) { Module( new MyModule() ) } 

天真的实现信号发生器


为了使电视至少能以某种方式理解我们,您需要支持平均技巧级别的“协议”:有三个重要的信号级别:


  • 1.0V-白色
  • 0.3V-黑色
  • 0V-特殊电平

为什么我称0V为特殊电压? 因为从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)) }) //  --  / io.L := (0.asUInt(10.W) + io.red + io.green + io.blue) / 4.asUInt io.millivolts := 0.asUInt } 

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, // 623 / 311 2, 30, 2, 30 // 624 / 312 ) val VSyncEnd = Seq( 30, 2, 30, 2, // 2 / 314 30, 2, 30, 2, // 3 / 315 2, 30, 2, 30, // 4 / 316 2, 30, 2, 30 // 5 / 317 ) val VSync1: Seq[Int] = VSyncStart ++ Seq( 2, 30, 2, 30, // 625 30, 2, 30, 2 // 1 ) ++ VSyncEnd ++ (6 to 23).flatMap(_ => Seq(4, 60)) val VSync2: Seq[Int] = VSyncStart ++ Seq( 2, 30, 30, 2 // 313 ) ++ VSyncEnd ++ (318 to 335).flatMap(_ => Seq(4, 60)) val BlackMv = 300.asUInt(12.W) val WhiteMv = 1000.asUInt(12.W) val FirstHalf = (24, 311) val SecondHalf = (336, 623) val TotalScanLineCount = 625 } class BWGenerator(clocksPerUs: Int) extends Module { import BWGenerator._ 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)) }) private val scanLineNr = RegInit(0.asUInt(10.W)) private val inScanLineCounter = RegInit(0.asUInt(16.W)) when (inScanLineCounter === (TotalScanLineLengthUs * clocksPerUs - 1).toInt.asUInt) { inScanLineCounter := 0.asUInt when(scanLineNr === (TotalScanLineCount - 1).asUInt) { scanLineNr := 0.asUInt } otherwise { scanLineNr := scanLineNr + 1.asUInt } } otherwise { inScanLineCounter := inScanLineCounter + 1.asUInt } private val fieldIActive = SecondHalf._2.asUInt <= scanLineNr || scanLineNr < FirstHalf._1.asUInt private val fieldIGenerator = withReset(!fieldIActive) { Module(new OneShotPulseGenerator(VSync1.map(_ * clocksPerUs), initial = false)) } private val fieldIIActive = FirstHalf._2.asUInt <= scanLineNr && scanLineNr < SecondHalf._1.asUInt private val fieldIIGenerator = withReset(!fieldIIActive) { Module(new OneShotPulseGenerator(VSync2.map(_ * clocksPerUs), initial = false)) } private val inFirstHalf = FirstHalf ._1.asUInt <= scanLineNr && scanLineNr < FirstHalf ._2.asUInt private val inSecondHalf = SecondHalf._1.asUInt <= scanLineNr && scanLineNr < SecondHalf._2.asUInt io.inScanLine := (inFirstHalf || inSecondHalf) && ((ScanLineHSyncEndUs * clocksPerUs).toInt.asUInt <= inScanLineCounter) io.x := Mux( io.inScanLine, inScanLineCounter - (ScanLineHSyncEndUs * clocksPerUs).toInt.asUInt, 0.asUInt ) / 4.asUInt io.y := Mux( io.inScanLine, Mux( inFirstHalf, ((scanLineNr - FirstHalf ._1.asUInt) << 1).asUInt, ((scanLineNr - SecondHalf._1.asUInt) << 1).asUInt + 1.asUInt ), 0.asUInt ) when (fieldIActive) { io.millivolts := Mux(fieldIGenerator .io.signal, BlackMv, 0.asUInt) }.elsewhen (fieldIIActive) { io.millivolts := Mux(fieldIIGenerator.io.signal, BlackMv, 0.asUInt) }.otherwise { when (inScanLineCounter < (ScanLineHSyncStartUs * clocksPerUs).toInt.asUInt) { io.millivolts := 0.asUInt }.elsewhen (inScanLineCounter < (ScanLineHSyncEndUs * clocksPerUs).toInt.asUInt) { io.millivolts := BlackMv }.otherwise { io.millivolts := (BlackMv + (io.L << 1).asUInt).asUInt } } } 

综合代码生成


一切都很好,但是我们希望将最终的设计缝到板上。 为此,您需要合成Verilog。 这是通过非常简单的方式完成的:


 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) } 

现在,您需要将其推送到Quartus项目中。 对于火星探测器2,我们需要Quartus 13.1的免费版本。 如何安装, 在火星探测器的网站上。 从那里,我下载了火星漫游者2板的“第一个项目”,将其放入存储库中,并进行了一些更正。 由于我不是电子工程师(而且我实际上对FPGA作为加速器比对接口卡更感兴趣),因此


就像那个笑话...

程序员在调试方面坐得很深。
适合儿子:
“爸爸,为什么太阳每天在东方升起而在西方落下?”
“你检查了吗?”
-已检查。
-检查得好吗?
-好
-有效吗?
-有效。
-每天都可以吗?
-是的,每天。
-那么,上帝啊,儿子,不要触摸任何东西,不要改变任何东西。


...我只是删除了VGA信号发生器并添加了我的模块。


切换Quatus


之后,我将模拟电视调谐器连接到另一台计算机(笔记本电脑),以便信号发生器和使用者的电源之间至少存在电气隔离,并且只是将信号从板的IO7(+)和GND(-)引脚发送到复合输入(减号)。外部联系人,再加上中心)。 好吧,就是说,“简单”……这仅仅是如果手从应该应该伸出的地方伸出,或者如果我有阴阳连接线。 但是我只有一堆公母电线。 但是我有坚韧和钳子! 通常,便秘只有一丝,我确实使自己成为了几乎两个工人,虽然很困难,但是却执着于董事会。 这是我所看到的:


第一个黑白图像


实际上,我当然欺骗了您。 上面显示的代码是在“在硬件上”调试大约三个小时后得到的,但是,该死,我写了它,并且可以正常工作! 而且,考虑到我过去几乎不熟悉严肃的电子技术,我认为这项任务并不可怕,这是一项艰巨的任务。


彩色视频生成


好吧,那么事情就变得很小了-添加彩色视频信号发生器。 我学习了本教程,并开始尝试形成一个色同步信号(在色彩信号的载波频率上添加到正弦波的黑色电平中,该信号在HSync期间会短暂产生),实际上是根据公式来形成。 但是,即使您破解了它也不会出来...在某个时候,我突然意识到,尽管频率并不能使我快速浏览文档,但电视却很难调到任意一台。 搜索后,我发现PAL使用4.43 MHz的载波频率。 我想这是帽子。 “操你,”调谐器回答。 经过一整天的调试,并且仅一次看到图片中的彩色阴影(此外,当我告诉调谐器它通常是NTSC时)


...我意识到绝望的样子

然后我意识到没有示波器是我无法做到的。 而且,正如我已经说过的,我对电子产品并不熟悉,当然,我在家中没有这样的技术奇迹。 买? 对于一个实验来说有点贵...从膝盖上可以构筑什么呢? 将信号连接到声卡的线路输入? 是的,只有4个半兆赫-不太可能开始(至少没有改动)。 嗯,火星漫游者有一个20 MHz的ADC,但是将串行接口速度的原始流传输到计算机还不够。 嗯,在某个地方,您仍然必须处理信号以进行显示,实际上会有相当多的信息位,但是这也弄乱了串行端口,为计算机编写程序……然后,我认为工程师应该开发本身就具有健康的韧性:有一台空闲的彩色成像仪,一台ADC……但是黑白图像却稳定地输出了……好吧,让信号发生器调试一下!


抒情离题(正如他们所说,“学生的意见不必与老师的意见,常识和Peano的公理学相吻合”):当我将色彩生成与各种乘法和其他复杂事物相加时,Fmax对于信号调理器产生了强烈的下垂。 什么是Fmax? 据我从Harris&Harris教科书中了解到的那样,FPGA的CAD倾向于在任何情况下都不按标准编写Verilog,而是“按概念”编写:例如,结果应为同步电路-一种来自组合逻辑 (加法,乘法)的定向无环网络,除法,逻辑运算...),其输入和输出分别卡在触发器的输出和输入上。 时钟信号沿上的触发器会记住整个下一个时钟周期的输入值,该信号的电平必须在前端之前的某个时间和之后的某个时间(这两个时间常数)保持稳定。 继时钟信号开始运行之后,来自触发器输出的信号又流向组合逻辑的输出(以及其他触发器的输入。因此,还有微电路的输出),它的特征还在于两个间隔:没有输出的时间将有时间开始更改,之后时间将平静下来(假设输入已更改一次)。 这是组合逻辑确保满足触发器要求的最大频率-这是Fmax。 当两个时钟之间的电路有时间计数时,Fmax会减小。 当然,我希望频率更大,但是如果它突然跳了10次(甚至CAD报告中的频域数量减少了),请检查一下,也许您弄乱了某个地方,结果CAD找到了一个常数表达式并高兴地将其用于优化。


示波器促销


不,不是在示波器出现扭曲和少量其他零件之后,但是示波器自举就像编译器自举一样,仅适用于示波器。


我们将根据命令制作一个示波器,记录一些输入信号样本,之后仅显示记录的内容。 由于他将需要以某种方式发出命令进行记录,然后-在其中导航,我们将需要一些按钮控制器-我写的不是很方便,但相当原始,这里是:


 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) { //  1 -  io.output := !( io.y > (windowPixelHeight + 10).asUInt && io.y < (windowPixelHeight + 20).asUInt && (io.x / clocksPerUs.asUInt)(0) ) } otherwise { // , ,  // signal / 2^inputWidth ~ y / windowPixelHeight // signal * windowPixelHeight ~ y * 2^inputWidth io.output := (currentValue * windowPixelHeight.asUInt >= ((io.y - 5.asUInt) << inputWidth).asUInt) && (currentValue * windowPixelHeight.asUInt <= ((io.y + 5.asUInt) << inputWidth).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 , . . — !


: " -?" — ! ...

Source: https://habr.com/ru/post/zh-CN414995/


All Articles