├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Readme.md ├── build.sbt ├── project └── build.properties └── src ├── chisel └── lib │ └── uart │ └── Uart.scala ├── design ├── Blink.scala ├── EchoInst.scala ├── HelloWorld.scala ├── LEDToggle.scala └── fpga4fun │ └── MakingAnLedHalfLit.scala └── fpga ├── boards └── icesugar │ └── IceSugar.scala ├── ip └── ice40 │ └── RGBLedDriver.scala └── tools └── ChiselCompiler.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: unit tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | # test every day at noon (PST) to check for new dependency changes 8 | - cron: '0 19 * * *' 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install Verilator 18 | run: | 19 | sudo apt-get install -y verilator 20 | verilator --version 21 | 22 | - name: Install Scala 23 | uses: olafurpg/setup-scala@v10 24 | with: 25 | java-version: openjdk@1.11 26 | 27 | - name: Compile 28 | run: sbt compile 29 | 30 | - name: Unit Tests 31 | run: sbt test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Scala 2 | /project/target/ 3 | /target/ 4 | 5 | 6 | # Intellij Idea 7 | /.idea/ 8 | 9 | # chisel testers 10 | /test_run_dir/ 11 | 12 | # fpga builds 13 | /build/ 14 | 15 | # Chisel compiler 16 | *.v 17 | *.fir 18 | *.json 19 | 20 | # treadle 21 | *.vcd 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, The Regents of the University of California 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Chisel Examples for the iCESugar FPGA Board 2 | 3 | This repository collects building blocks and example designs written in the [Chisel](https://github.com/chipsalliance/chisel3) 4 | hardware construction language and targeting the [iCESugar FPGA Board](https://github.com/wuxx/icesugar). 5 | 6 | ## Install Dependencies 7 | 8 | To compile the Chisel designs you need an up to date JDK (OpenJDK 8 or newer) as well as a version of the 9 | [Scala Build Tool (sbt)](https://www.scala-sbt.org/download.html). 10 | 11 | To turn designs into FPGA configurations you need [yosys](https://github.com/YosysHQ/yosys) for synthesis, 12 | [nextpnr-ice40](https://github.com/YosysHQ/nextpnr) and [icepack](https://github.com/YosysHQ/icestorm). 13 | You can either build these tools from source or download [pre-built binaries](https://github.com/FPGAwars/apio). 14 | 15 | ## Building and Uploading the Blink Example 16 | 17 | To build and upload the blink example, you need to edit the `iceLinkPath` in `src/design/Blink.scala` 18 | to point to the directory that your iCELink is mounted to. 19 | 20 | Now you can compile and program the blink example using `sbt`: 21 | ```shell 22 | sbt "run designs.BlinkGenerator" 23 | ``` 24 | 25 | Alternatively, you can open the project in an IDE like 26 | [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) 27 | with the [Scala Plugin](https://plugins.jetbrains.com/plugin/1347-scala) by clicking on the `build.sbt` file. 28 | Then you will be able to directly execute the `BlinkGenerator` from your IDE. 29 | In IntelliJ, you will see a green arrow next to the `object BlinkGenerator extends App {` line. 30 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "icesugar-chisel" 2 | version := "0.1" 3 | scalaVersion := "2.13.5" 4 | 5 | scalacOptions := Seq("-deprecation", "-unchecked", "-feature", "-language:reflectiveCalls") 6 | 7 | // using the rollings SNAPSHOT release for the latest features 8 | resolvers += Resolver.sonatypeRepo("snapshots") 9 | libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.5-SNAPSHOT" 10 | libraryDependencies += "edu.berkeley.cs" %% "chiseltest" % "0.5-SNAPSHOT" % Test 11 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.2" % Test 12 | addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % "3.5-SNAPSHOT" cross CrossVersion.full) 13 | 14 | scalaSource in Compile := baseDirectory.value / "src" 15 | scalaSource in Test := baseDirectory.value / "test" 16 | resourceDirectory in Test := baseDirectory.value / "test" / "resources" 17 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.5 2 | -------------------------------------------------------------------------------- /src/chisel/lib/uart/Uart.scala: -------------------------------------------------------------------------------- 1 | /* Source: https://github.com/freechipsproject/ip-contributions 2 | * Released under http://unlicense.org/ 3 | * 4 | * A UART is a serial port, also called an RS232 interface. 5 | * 6 | * Author: Martin Schoeberl (martin@jopdesign.com) 7 | * 8 | */ 9 | 10 | package chisel.lib.uart 11 | 12 | import chisel3._ 13 | import chisel3.util._ 14 | 15 | class UartIO extends DecoupledIO(UInt(8.W)) { 16 | override def cloneType: this.type = new UartIO().asInstanceOf[this.type] 17 | } 18 | 19 | 20 | /** 21 | * Transmit part of the UART. 22 | * A minimal version without any additional buffering. 23 | * Use a ready/valid handshaking. 24 | */ 25 | class Tx(frequency: Int, baudRate: Int) extends Module { 26 | val io = IO(new Bundle { 27 | val txd = Output(UInt(1.W)) 28 | val channel = Flipped(new UartIO()) 29 | }) 30 | 31 | val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).asUInt() 32 | 33 | val shiftReg = RegInit(0x7ff.U) 34 | val cntReg = RegInit(0.U(20.W)) 35 | val bitsReg = RegInit(0.U(4.W)) 36 | 37 | io.channel.ready := (cntReg === 0.U) && (bitsReg === 0.U) 38 | io.txd := shiftReg(0) 39 | 40 | when(cntReg === 0.U) { 41 | 42 | cntReg := BIT_CNT 43 | when(bitsReg =/= 0.U) { 44 | val shift = shiftReg >> 1 45 | shiftReg := Cat(1.U, shift(9, 0)) 46 | bitsReg := bitsReg - 1.U 47 | }.otherwise { 48 | when(io.channel.valid) { 49 | shiftReg := Cat(Cat(3.U, io.channel.bits), 0.U) // two stop bits, data, one start bit 50 | bitsReg := 11.U 51 | }.otherwise { 52 | shiftReg := 0x7ff.U 53 | } 54 | } 55 | 56 | }.otherwise { 57 | cntReg := cntReg - 1.U 58 | } 59 | } 60 | 61 | /** 62 | * Receive part of the UART. 63 | * A minimal version without any additional buffering. 64 | * Use a ready/valid handshaking. 65 | * 66 | * The following code is inspired by Tommy's receive code at: 67 | * https://github.com/tommythorn/yarvi 68 | */ 69 | class Rx(frequency: Int, baudRate: Int) extends Module { 70 | val io = IO(new Bundle { 71 | val rxd = Input(UInt(1.W)) 72 | val channel = new UartIO() 73 | }) 74 | 75 | val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).U 76 | val START_CNT = ((3 * frequency / 2 + baudRate / 2) / baudRate - 1).U 77 | 78 | // Sync in the asynchronous RX data, reset to 1 to not start reading after a reset 79 | val rxReg = RegNext(RegNext(io.rxd, 1.U), 1.U) 80 | 81 | val shiftReg = RegInit(0.U(8.W)) 82 | val cntReg = RegInit(0.U(20.W)) 83 | val bitsReg = RegInit(0.U(4.W)) 84 | val valReg = RegInit(false.B) 85 | 86 | when(cntReg =/= 0.U) { 87 | cntReg := cntReg - 1.U 88 | }.elsewhen(bitsReg =/= 0.U) { 89 | cntReg := BIT_CNT 90 | shiftReg := Cat(rxReg, shiftReg >> 1) 91 | bitsReg := bitsReg - 1.U 92 | // the last shifted in 93 | when(bitsReg === 1.U) { 94 | valReg := true.B 95 | } 96 | }.elsewhen(rxReg === 0.U) { // wait 1.5 bits after falling edge of start 97 | cntReg := START_CNT 98 | bitsReg := 8.U 99 | } 100 | 101 | when(valReg && io.channel.ready) { 102 | valReg := false.B 103 | } 104 | 105 | io.channel.bits := shiftReg 106 | io.channel.valid := valReg 107 | } 108 | 109 | /** 110 | * A single byte buffer with a ready/valid interface 111 | */ 112 | class Buffer extends Module { 113 | val io = IO(new Bundle { 114 | val in = Flipped(new UartIO()) 115 | val out = new UartIO() 116 | }) 117 | 118 | val empty :: full :: Nil = Enum(2) 119 | val stateReg = RegInit(empty) 120 | val dataReg = RegInit(0.U(8.W)) 121 | 122 | io.in.ready := stateReg === empty 123 | io.out.valid := stateReg === full 124 | 125 | when(stateReg === empty) { 126 | when(io.in.valid) { 127 | dataReg := io.in.bits 128 | stateReg := full 129 | } 130 | }.otherwise { // full 131 | when(io.out.ready) { 132 | stateReg := empty 133 | } 134 | } 135 | io.out.bits := dataReg 136 | } 137 | 138 | /** 139 | * A transmitter with a single buffer. 140 | */ 141 | class BufferedTx(frequency: Int, baudRate: Int) extends Module { 142 | val io = IO(new Bundle { 143 | val txd = Output(UInt(1.W)) 144 | val channel = Flipped(new UartIO()) 145 | }) 146 | val tx = Module(new Tx(frequency, baudRate)) 147 | val buf = Module(new Buffer()) 148 | 149 | buf.io.in <> io.channel 150 | tx.io.channel <> buf.io.out 151 | io.txd <> tx.io.txd 152 | } 153 | 154 | /** 155 | * Send a string. 156 | */ 157 | class Sender(frequency: Int, baudRate: Int, output: String="Hello World!") extends Module { 158 | val io = IO(new Bundle { 159 | val txd = Output(UInt(1.W)) 160 | }) 161 | 162 | val tx = Module(new BufferedTx(frequency, baudRate)) 163 | 164 | io.txd := tx.io.txd 165 | 166 | val msg = output 167 | val text = VecInit(msg.map(_.U)) 168 | val len = msg.length.U 169 | 170 | val cntReg = RegInit(0.U(8.W)) 171 | 172 | tx.io.channel.bits := text(cntReg) 173 | tx.io.channel.valid := cntReg =/= len 174 | 175 | when(tx.io.channel.ready && cntReg =/= len) { 176 | cntReg := cntReg + 1.U 177 | } 178 | } 179 | 180 | /** 181 | * Send what is received. 182 | */ 183 | class Echo(frequency: Int, baudRate: Int) extends Module { 184 | val io = IO(new Bundle { 185 | val txd = Output(UInt(1.W)) 186 | val rxd = Input(UInt(1.W)) 187 | }) 188 | // io.txd := RegNext(io.rxd) 189 | val tx = Module(new BufferedTx(frequency, baudRate)) 190 | val rx = Module(new Rx(frequency, baudRate)) 191 | io.txd := tx.io.txd 192 | rx.io.rxd := io.rxd 193 | tx.io.channel <> rx.io.channel 194 | } 195 | 196 | class UartMain(frequency: Int, baudRate: Int) extends Module { 197 | val io = IO(new Bundle { 198 | val rxd = Input(UInt(1.W)) 199 | val txd = Output(UInt(1.W)) 200 | }) 201 | 202 | val doSender = true 203 | 204 | if (doSender) { 205 | val s = Module(new Sender(frequency, baudRate)) 206 | io.txd := s.io.txd 207 | } else { 208 | val e = Module(new Echo(frequency, baudRate)) 209 | e.io.rxd := io.rxd 210 | io.txd := e.io.txd 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/design/Blink.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package design 6 | 7 | import chisel3._ 8 | import chisel3.experimental.Analog 9 | import fpga.boards.icesugar._ 10 | import fpga.ip.ice40._ 11 | 12 | class Blink extends IceSugarTop { 13 | val rgb = IO(Vec(3, Analog(1.W))) 14 | 15 | val leds = Module(new RGBLedDriver(8, 8, 8)) 16 | rgb <> leds.io.toLed 17 | leds.io.powerUp := true.B 18 | leds.io.enabled := true.B 19 | val (blue, red, green) = (leds.io.pwm(0), leds.io.pwm(1), leds.io.pwm(2)) 20 | 21 | val counter = RegInit(0.U(26.W)) 22 | counter := counter + 1.U 23 | red := counter(24) 24 | green := counter(23) 25 | blue := counter(25) 26 | } 27 | 28 | object BlinkGenerator extends App { 29 | val pcf = 30 | """set_io clock 35 31 | |set_io rgb_0 39 32 | |set_io rgb_1 40 33 | |set_io rgb_2 41 34 | |""".stripMargin 35 | val bin = IceSugar.makeBin(new Blink, pcf) 36 | val iceLinkPath = "/run/media/brandon/iCELink/" 37 | IceSugar.program(bin, iceLinkPath) 38 | } 39 | -------------------------------------------------------------------------------- /src/design/EchoInst.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Brandon Fajardo 4 | 5 | package design 6 | 7 | import chisel3._ 8 | import chisel.lib.uart.Echo 9 | import fpga.boards.icesugar._ 10 | 11 | class EchoInst extends IceSugarTop { 12 | val echo = Module(new Echo(frequency = clockFrequency.toInt, baudRate = 115200)) 13 | 14 | val tx = IO(Output(UInt(1.W))) 15 | tx := echo.io.txd 16 | val rx = IO(Input(UInt(1.W))) 17 | echo.io.rxd := rx 18 | } 19 | 20 | object EchoInstGenerator extends App { 21 | val pcf = 22 | """set_io clock 35 23 | |set_io tx 6 24 | |set_io rx 4 25 | |""".stripMargin 26 | val bin = IceSugar.makeBin(new EchoInst, pcf) 27 | val iceLinkPath = "/run/media/brandon/iCELink/" 28 | IceSugar.program(bin, iceLinkPath) 29 | } 30 | -------------------------------------------------------------------------------- /src/design/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package design 6 | 7 | import chisel.lib.uart.Sender 8 | import chisel3._ 9 | import fpga.boards.icesugar._ 10 | 11 | /** Uses a UART transmitter from the ip-contributions library to send "Hello World!" on startup. */ 12 | class HelloWorld extends IceSugarTop { 13 | val sender = Module(new Sender(frequency = clockFrequency.toInt, baudRate = 115200, "Hello World Test")) 14 | val tx = IO(Output(UInt(1.W))) 15 | tx := sender.io.txd 16 | } 17 | 18 | object HelloWorldGenerator extends App { 19 | val pcf = 20 | """set_io clock 35 21 | |set_io tx 6 22 | |""".stripMargin 23 | val bin = IceSugar.makeBin(new HelloWorld, pcf) 24 | val iceLinkPath = "/run/media/brandon/iCELink/" 25 | IceSugar.program(bin, iceLinkPath) 26 | } 27 | -------------------------------------------------------------------------------- /src/design/LEDToggle.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Brandon Fajardo 4 | 5 | package design 6 | 7 | import chisel3._ 8 | import chisel.lib.uart.{BufferedTx, Rx, UartIO} 9 | import chisel3.experimental.Analog 10 | import chisel3.util._ 11 | import fpga.boards.icesugar._ 12 | import fpga.ip.ice40.RGBLedDriver 13 | 14 | class LEDToggle extends IceSugarTop { 15 | val toggle = Module(new LEDInput(frequency = clockFrequency.toInt, baudRate = 115200)) 16 | 17 | val tx = IO(Output(UInt(1.W))) 18 | tx := toggle.io.txd 19 | val rx = IO(Input(UInt(1.W))) 20 | toggle.io.rxd := rx 21 | toggle.io.channel.ready := true.B 22 | 23 | val rgb = IO(Vec(3, Analog(1.W))) 24 | toggle.io.powerUp := true.B 25 | toggle.io.enabled := true.B 26 | rgb <> toggle.io.toLed 27 | val (blue, red, green) = (toggle.io.pwm(0), toggle.io.pwm(1), toggle.io.pwm(2)) 28 | 29 | //Begin: Toggle LEDS logic 30 | val REDtoggle = RegInit(false.B) 31 | val GREENtoggle = RegInit(false.B) 32 | val BLUEtoggle = RegInit(false.B) 33 | 34 | when (toggle.io.channel.fire()) { 35 | switch (toggle.io.channel.bits) { 36 | is (114.U) {REDtoggle := !REDtoggle} //Toggle red on (lowercase) r press 37 | is (103.U) {GREENtoggle := !GREENtoggle} //Toggle green on (lowercase) g press 38 | is (98.U) {BLUEtoggle := !BLUEtoggle} //Toggle blue on (lowercase) b press 39 | } 40 | } 41 | 42 | red := REDtoggle 43 | green := GREENtoggle 44 | blue := BLUEtoggle 45 | } 46 | 47 | /** Combine RGB and Input/Output. */ 48 | class LEDInput(frequency: Int, baudRate: Int) extends Module { 49 | val io = IO(new Bundle { 50 | val txd = Output(UInt(1.W)) 51 | val rxd = Input(UInt(1.W)) 52 | val channel = new UartIO() 53 | val powerUp = Input(Bool()) 54 | val enabled = Input(Bool()) 55 | val pwm = Input(Vec(3, Bool())) 56 | val toLed = Vec(3, Analog(1.W)) 57 | }) 58 | val tx = Module(new BufferedTx(frequency, baudRate)) 59 | val rx = Module(new Rx(frequency, baudRate)) 60 | io.txd := tx.io.txd 61 | rx.io.rxd := io.rxd 62 | tx.io.channel <> rx.io.channel 63 | io.channel <> rx.io.channel 64 | 65 | val leds = Module(new RGBLedDriver(8, 8, 8)) 66 | leds.io.powerUp := io.powerUp 67 | leds.io.enabled := io.enabled 68 | leds.io.pwm := io.pwm 69 | leds.io.toLed <> io.toLed 70 | } 71 | 72 | object LEDToggleGenerator extends App { 73 | val pcf = 74 | """set_io clock 35 75 | |set_io rx 4 76 | |set_io tx 6 77 | |set_io rgb_0 39 78 | |set_io rgb_1 40 79 | |set_io rgb_2 41 80 | |""".stripMargin 81 | val bin = IceSugar.makeBin(new LEDToggle, pcf) 82 | val iceLinkPath = "/run/media/brandon/iCELink/" 83 | IceSugar.program(bin, iceLinkPath) 84 | } 85 | -------------------------------------------------------------------------------- /src/design/fpga4fun/MakingAnLedHalfLit.scala: -------------------------------------------------------------------------------- 1 | package design.fpga4fun 2 | 3 | import chisel3._ 4 | import chisel3.experimental.Analog 5 | import fpga.boards.icesugar._ 6 | import fpga.ip.ice40._ 7 | 8 | // inspired by https://www.fpga4fun.com/Opto2.html 9 | class MakingAnLedHalfLit extends IceSugarTop { 10 | val rgb = IO(Vec(3, Analog(1.W))) 11 | 12 | val leds = Module(new RGBLedDriver(8, 8, 8)) 13 | rgb <> leds.io.toLed 14 | leds.io.powerUp := true.B 15 | leds.io.enabled := true.B 16 | val (blue, red, green) = (leds.io.pwm(0), leds.io.pwm(1), leds.io.pwm(2)) 17 | green := 0.U 18 | blue := 0.U 19 | 20 | // the actual LED toggle code 21 | val toggle = Reg(UInt(1.W)) 22 | toggle := ~toggle 23 | red := toggle 24 | } 25 | 26 | 27 | object MakingAnLedHalfLitGenerator extends App { 28 | val pcf = 29 | """set_io clock 35 30 | |set_io rgb_0 39 31 | |set_io rgb_1 40 32 | |set_io rgb_2 41 33 | |""".stripMargin 34 | val bin = IceSugar.makeBin(new MakingAnLedHalfLit, pcf) 35 | val iceLinkPath = "/run/media/kevin/iCELink/" 36 | IceSugar.program(bin, iceLinkPath) 37 | } 38 | -------------------------------------------------------------------------------- /src/fpga/boards/icesugar/IceSugar.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package fpga.boards.icesugar 6 | 7 | import chisel3._ 8 | import chisel3.experimental.{ChiselAnnotation, annotate} 9 | import firrtl.annotations.PresetAnnotation 10 | import firrtl.options.TargetDirAnnotation 11 | import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlStage, OutputFileAnnotation} 12 | import fpga.tools.ChiselCompiler 13 | 14 | import java.io.{File, PrintWriter} 15 | import java.nio.file.Files 16 | import scala.sys.process._ 17 | 18 | /** iCESugar FPGA Board Support for Chisel: https://github.com/wuxx/icesugar */ 19 | object IceSugar { 20 | /** generates Verilog, then runs yosys and nextpnr */ 21 | def makeBin(makeTop: => IceSugarTop, pcf: String, name: String = "", buildDir: String = "build"): BinFile = { 22 | // elaborate circuit first in order to get the name of the toplevel module 23 | val (state, top) = ChiselCompiler.elaborate(() => makeTop) 24 | 25 | // create a build directory 26 | val topName = if(name.isEmpty) top.name else name 27 | val dir = new File(buildDir, topName) 28 | dir.mkdirs() 29 | 30 | // run firrtl compiler 31 | val annos = state.annotations ++ Seq( 32 | FirrtlCircuitAnnotation(state.circuit), 33 | TargetDirAnnotation(dir.getAbsolutePath), 34 | ) 35 | val res = (new FirrtlStage).execute(Array("-E", "sverilog"), annos) 36 | val verilogFile = res.collectFirst { case OutputFileAnnotation(file) => file + ".sv" }.get 37 | 38 | // generate pcf file 39 | val pcfFile = topName + ".pcf" 40 | // TODO: generate automatically 41 | val pcfWriter = new PrintWriter(dir.getAbsolutePath + "/" + pcfFile) 42 | pcfWriter.write(pcf) 43 | pcfWriter.close() 44 | 45 | // run yosys 46 | val jsonFile = topName + ".json" 47 | val yosysCmd = List("yosys", "-l", "yosys.log", "-p", s"synth_ice40 -json $jsonFile", verilogFile) 48 | val yosysRet = Process(yosysCmd, dir) ! ProcessLogger(_ => ()) 49 | if(yosysRet != 0) { 50 | throw new RuntimeException(s"Failed to run yosys! Please consult: ${dir.getAbsolutePath}/yosys.log for details.") 51 | } 52 | 53 | // run nextpnr 54 | val ascFile = topName + ".asc" 55 | val freq = top.clockFrequency / 1000 / 1000 56 | require(freq * 1000 * 1000 == top.clockFrequency) 57 | val nextpnrCmd = List("nextpnr-ice40", 58 | "-l", "next.log", 59 | "--up5k", 60 | "--package", "sg48", 61 | "--freq", freq.toString, 62 | "--pcf", pcfFile, 63 | "--json", jsonFile, 64 | "--asc", ascFile 65 | ) 66 | val nextRet = Process(nextpnrCmd, dir) ! ProcessLogger(_ => ()) 67 | if(nextRet != 0) { 68 | throw new RuntimeException(s"Failed to run nextpnr! Please consult: ${dir.getAbsolutePath}/next.log for details.") 69 | } 70 | 71 | // run icepack 72 | val binFile = topName + ".bin" 73 | val icepackCmd = List("icepack", ascFile, binFile) 74 | val icepackRet = (Process(icepackCmd, dir) #> new File(dir.getAbsolutePath + "/ice.log")).! 75 | if(icepackRet != 0) { 76 | throw new RuntimeException(s"Failed to run icepack! Please consult: ${dir.getAbsolutePath}/ice.log for details.") 77 | } 78 | 79 | BinFile(dir.getPath + "/" + binFile) 80 | } 81 | 82 | /** program IceSugar board */ 83 | def program(bin: BinFile, iceLinkPath: String): Unit = { 84 | val binFile = new File(bin.path) 85 | if(!binFile.isFile) { 86 | throw new RuntimeException(s"Failed to find binary file at ${binFile.getAbsoluteFile}") 87 | } 88 | 89 | val linkDir = new File(iceLinkPath) 90 | if(!linkDir.isDirectory) { 91 | throw new RuntimeException(s"Failed to find iCELink at ${linkDir.getAbsolutePath}") 92 | } 93 | 94 | val dst = new File(linkDir, "image.bin") 95 | Files.copy(binFile.toPath, dst.toPath) 96 | } 97 | } 98 | 99 | /** icepack output */ 100 | case class BinFile(path: String) 101 | 102 | 103 | trait IceSugarTop extends Module with RequireAsyncReset { 104 | // 12 MHz Clock 105 | def clockFrequency: Long = 12000000 106 | 107 | // FPGA Bitstream Reset 108 | // The IceSugar board has a push button connected to CRESET_B which will 109 | // initiate a configuration download. 110 | // See section "4.6. External Reset" in the "iCE40 UltraPlus(TM) Family" data sheet. 111 | annotate(new ChiselAnnotation { 112 | override def toFirrtl = PresetAnnotation(reset.toTarget) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /src/fpga/ip/ice40/RGBLedDriver.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package fpga.ip.ice40 6 | 7 | import chisel3._ 8 | import chisel3.experimental.{Analog, ExtModule} 9 | 10 | /** Chisel wrapper around the built-in RGB Driver IP 11 | * Please note that the `toLed` wires are hard wired: https://github.com/YosysHQ/nextpnr/issues/254 12 | * */ 13 | class RGBLedDriver(current0: Int, current1: Int, current2: Int, halfCurrentMode: Boolean = false) extends Module { 14 | val io = IO(new RGBLedDriverIO) 15 | 16 | // determine possible current steps 17 | val currentStepSize = if(halfCurrentMode) 2 else 4 18 | val minCurrent = 0 19 | val maxCurrent = currentStepSize * 6 20 | 21 | private def convertCurrent(current: Int): String = { 22 | require(current >= 0, "Current may not be negative!") 23 | require(current <= maxCurrent, s"Current of $current mA exceeds maximum ($maxCurrent mA)!") 24 | require(current % currentStepSize == 0, s"Current of $current mA is not a multiple of $currentStepSize mA!") 25 | val ones = current / currentStepSize 26 | (Seq("0b") ++ Seq.fill(6 - ones)("0") ++ Seq.fill(ones)("1")).mkString("") 27 | } 28 | 29 | val ip = Module(new SB_RGBA_DRV( 30 | mode = if(halfCurrentMode) "0b1" else "0b0", 31 | rgb0 = convertCurrent(current0), 32 | rgb1 = convertCurrent(current1), 33 | rgb2 = convertCurrent(current2), 34 | )) 35 | 36 | ip.CURREN := io.powerUp 37 | ip.RGBLEDEN := io.enabled 38 | ip.RGB0PWM := io.pwm(0) 39 | ip.RGB1PWM := io.pwm(1) 40 | ip.RGB2PWM := io.pwm(2) 41 | io.toLed(0) <> ip.RGB0 42 | io.toLed(1) <> ip.RGB1 43 | io.toLed(2) <> ip.RGB2 44 | } 45 | 46 | class RGBLedDriverIO extends Bundle { 47 | val powerUp = Input(Bool()) 48 | val enabled = Input(Bool()) 49 | val pwm = Input(Vec(3, Bool())) 50 | val toLed = Vec(3, Analog(1.W)) 51 | } 52 | 53 | /** Chisel declaration of the built-in RGB Led Driver IP 54 | * See "iCE40 LED Driver Usage Guide" for more information. 55 | * */ 56 | class SB_RGBA_DRV(mode: String = "0b0", rgb0: String = "0b000011", rgb1: String = "0b000011", rgb2: String = "0b000011") 57 | extends ExtModule(Map("CURRENT_MODE" -> mode, "RGB0_CURRENT" -> rgb0, "RGB1_CURRENT" -> rgb1, "RGB2_CURRENT" -> rgb2)) { 58 | val CURREN = IO(Input(Bool())) 59 | val RGBLEDEN = IO(Input(Bool())) 60 | val RGB0PWM = IO(Input(Bool())) 61 | val RGB1PWM = IO(Input(Bool())) 62 | val RGB2PWM = IO(Input(Bool())) 63 | val RGB0 = IO(Analog(1.W)) 64 | val RGB1 = IO(Analog(1.W)) 65 | val RGB2 = IO(Analog(1.W)) 66 | } -------------------------------------------------------------------------------- /src/fpga/tools/ChiselCompiler.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package fpga.tools 6 | 7 | import chisel3.RawModule 8 | import chisel3.stage._ 9 | import chisel3.stage.phases.Convert 10 | import firrtl.annotations.Annotation 11 | import firrtl.stage.FirrtlCircuitAnnotation 12 | 13 | /** Allows programmatic access to the Builder elaboration and the Converter to Firrtl */ 14 | object ChiselCompiler { 15 | private val converter = new Convert 16 | def elaborate[M <: RawModule](gen: () => M): (firrtl.CircuitState, M) = { 17 | // run Builder.build(Module(gen())) 18 | val genAnno = ChiselGeneratorAnnotation(gen) 19 | val elaborationAnnos = genAnno.elaborate 20 | 21 | // extract elaborated module 22 | val dut = elaborationAnnos.collectFirst{ case DesignAnnotation(d) => d}.get 23 | 24 | // run Converter.convert(a.circuit) and toFirrtl on all annotations 25 | val converterAnnos = converter.transform(elaborationAnnos) 26 | val chirrtl = converterAnnos.collectFirst { case FirrtlCircuitAnnotation(c) => c }.get 27 | val annos = converterAnnos.filterNot(isInternalAnno) 28 | val state = firrtl.CircuitState(chirrtl, annos) 29 | 30 | (state, dut.asInstanceOf[M]) 31 | } 32 | private def isInternalAnno(a: Annotation): Boolean = a match { 33 | case _: FirrtlCircuitAnnotation | _: DesignAnnotation[_] | _:ChiselCircuitAnnotation => true 34 | case _=> false 35 | } 36 | } --------------------------------------------------------------------------------