├── .gitignore ├── .scalafix.conf ├── .scalafmt.conf ├── LICENSE ├── Makefile ├── README.md ├── build.sbt ├── build.sc ├── constraints ├── ecp5-ulx3s.lpf ├── storey_peak_stratixV.sdc └── storey_peak_stratixV.tcl ├── img ├── Diagram1.png └── Terminal1.png ├── pinfinder.core ├── project ├── build.properties └── plugins.sbt ├── scripts └── mill └── src ├── main └── scala │ ├── Toplevel.scala │ └── Uart.scala └── test └── scala ├── BlinkySpec.scala └── UtilitiesSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | target/ 3 | project/target 4 | project/project 5 | docs/generated 6 | docs-target/ 7 | /*.fir 8 | /*.v 9 | /*.anno.json 10 | /*.swp 11 | /*~ 12 | .addons-dont-touch 13 | /lib/ 14 | /test_lib/ 15 | /testbuild/ 16 | obj_dir 17 | test_run_dir 18 | generated 19 | .metals 20 | .bloop 21 | .bsp 22 | .vscode 23 | -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | RemoveUnused, 3 | DisableSyntax, 4 | LeakingImplicitClassVal, 5 | NoAutoTupling, 6 | NoValInForComprehension, 7 | OrganizeImports, 8 | ProcedureSyntax, 9 | ] 10 | 11 | OrganizeImports { 12 | groupedImports = Merge 13 | } 14 | 15 | RemoveUnused { 16 | imports = false // handled by OrganizeImports 17 | } -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.7.5" 2 | maxColumn = 120 3 | align.preset = most 4 | align.multiline = false 5 | continuationIndent.defnSite = 2 6 | assumeStandardLibraryStripMargin = true 7 | docstrings = JavaDoc 8 | lineEndings = preserve 9 | includeCurlyBraceInSelectChains = false 10 | danglingParentheses.preset = true 11 | optIn.annotationNewlines = true 12 | newlines.alwaysBeforeMultilineDef = false 13 | 14 | rewrite.rules = [RedundantBraces] 15 | 16 | rewrite.redundantBraces.generalExpressions = false 17 | rewriteTokens = { 18 | "⇒": "=>" 19 | "→": "->" 20 | "←": "<-" 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2021 Carlos Eduardo de Paula (carlosedp@gmail.com) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Define board parameters 2 | SHELL = bash 3 | 4 | # Project name and toplevel 5 | project = toplevel 6 | toplevel = Toplevel 7 | 8 | scala_files = $(wildcard src/main/scala/*scala) 9 | generated_files = generated 10 | verilog_files = $(generated_files)/*.v 11 | 12 | # Targets 13 | chisel: $(verilog_files) ## Generates Verilog code from Chisel sources using Mill 14 | $(verilog_files): $(scala_files) clean 15 | scripts/mill $(project).run -td $(generated_files) 16 | 17 | chisel-sbt: ## Generates Verilog code from Chisel sources using SBT 18 | sbt "run -board ${BOARD} -td $(generated_files)" 19 | 20 | chisel_tests: 21 | scripts/mill $(project).test 22 | 23 | check: chisel_tests ## Run Chisel tests 24 | 25 | fmt: 26 | scripts/mill all $(project).{reformat,fix} 27 | 28 | clean: ## Clean all generated files 29 | @./scripts/mill clean 30 | @rm -rf obj_dir test_run_dir target 31 | @rm -rf $(generated_files) 32 | @rm -rf out 33 | @rm -f $(project) 34 | 35 | .PHONY: chisel clean help 36 | 37 | .DEFAULT_GOAL := chisel 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chisel FPGA Pin Finder 2 | 3 | The idea for this project came from RISC-V Brasil Telegram list (thanks [@racerxdl](https://github.com/racerxdl) and [@samsoniuk](https://github.com/samsoniuk)) for a wish to map the unidentified pins on the Storey Peak Stratix V FPGA board. 4 | 5 | ![Connection](img/Diagram1.png) 6 | 7 | The module uses a Serial TX attached to each pin you want to find printing to it it's name. One can then take a USB-Serial module connected to the computer USB and a serial console (115200 8N1) open then go probing each of the "guessed" pin. If the terminal prints the pin name, you found it. 8 | 9 | ![Terminal](img/Terminal1.png) 10 | 11 | To configure, add the pins to the IO section and constraints file and instantiate the PinFinder class in that pin. 12 | 13 | It was tested on a Radiona ULX3S (where I already knew some pins) and then on the Storey Peak LED pins (that were already known too). 14 | 15 | Next the idea is to map the other pins like PCIe, QSFP control pins and etc. 16 | 17 | Quick demo: 18 | 19 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/0Ij2CQuhhj4/0.jpg)](https://youtu.be/0Ij2CQuhhj4) 20 | 21 | ## Usage 22 | 23 | The project can be used to map any unknown pins on an FPGA where it's location in the PCB is not known but accessible for a probe. One can select pins from a bank, configure in this tool and probe the PCB to search the pin. 24 | 25 | Select the pins to be testes (likely from a known bank), as trial-and-error, change the variables `pinsToTest` and `frequency` in the `Toplevel.scala` file like: 26 | 27 | ```scala 28 | // Change the `pinsToTest` variable to test different pins. 29 | // Do not change the rest of the code. 30 | // Define pins to test. Must match the pin name from the constraints file for your board. 31 | val pinsToTest = Seq("A10", "B10", "AW26", "AV26") 32 | 33 | // Define the frequency of the clock signal in Hz for the board you are using. 34 | // For ULX3S, use 25000000 35 | // For StoreyPeak, use 125000000 36 | val frequency = 125000000 37 | ``` 38 | 39 | Then add to the constraints (for Quartus as example): 40 | 41 | ```tcl 42 | set_location_assignment PIN_AW26 -to io_AW26 43 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_AW26 44 | set_instance_assignment -name SLEW_RATE 0 -to io_AW26 45 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_AW26 46 | ``` 47 | 48 | Then build the module and program to the FPGA. After programming, go "poking" the pins with the probe attached to a serial RX in the terminal (could be Putty, Minicom, etc) to try to identify the configured pins. 49 | 50 | ## Building 51 | 52 | The first and recommended is using [Fusesoc](https://github.com/olofk/fusesoc), a package manager that handles all board backend files and configuration. It also makes adding support to new boards and vendors much easier. 53 | 54 | ### Fusesoc build and generation 55 | 56 | To install Fusesoc (requires Python3 and pip3): 57 | 58 | ```sh 59 | pip3 install --upgrade --user fusesoc 60 | 61 | export PATH=~/.local/bin:$PATH 62 | ``` 63 | 64 | Check if it's working: 65 | 66 | ```sh 67 | $ fusesoc --version 68 | 1.12.0 69 | ``` 70 | 71 | To generate the programming files for the **ULX3s**, first adjust the comments on `Toplevel.scala` file (pins on IO, pins to be scanned and the clock in the end of the file). 72 | 73 | ```sh 74 | mkdir fusesoc-chiselblinky && cd fusesoc-chiselblinky 75 | 76 | fusesoc library add fusesoc-cores https://github.com/fusesoc/fusesoc-cores 77 | fusesoc library add pinfinder https://github.com/carlosedp/chisel-fpga-pinfinder 78 | 79 | # Download the container command wrapper 80 | wget https://gist.github.com/carlosedp/c0e29d55e48309a48961f2e3939acfe9/raw/bfeb1cfe2e188c1d5ced0b09aabc9902fdfda6aa/runme.py 81 | chmod +x runme.py 82 | 83 | # Run fusesoc with the wrapper as an environment var 84 | EDALIZE_LAUNCHER=$(realpath ../runme.py) fusesoc run --target=ulx3s_85 carlosedp:demo:pinfinder 85 | 86 | # Program to ULX3S 87 | ujprog ./build/carlosedp_demo_pinfinder_0/ulx3s_85-trellis/carlosedp_demo_pinfinder_0.svf 88 | ``` 89 | 90 | For the Storey Peak board, run: 91 | 92 | ```sh 93 | fusesoc run --target=storey_peak_stratixV --setup carlosedp:demo:pinfinder 94 | ``` 95 | 96 | This will generate the base files, `cd` into the generated directory (`./build/carlosedp_demo_pinfinder_0/storey_peak_stratixV-quartus`) in the machine Quartus is installed and run `make project`. Open the `carlosedp_demo_pinfinder_0.qpf` project for synthesize and PnR. Then program to the FPGA. 97 | 98 | The board can be programmed with the [JTAG to Quartus](https://github.com/j-marjanovic/jtag-quartus-ft232h) driver. 99 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // See README.md for license details. 2 | 3 | lazy val blinky = (project in file(".")) 4 | .settings( 5 | organization := "com.carlosedp", 6 | name := "chisel-blinky", 7 | version := "0.0.1", 8 | scalaVersion := "2.12.13", 9 | semanticdbEnabled := true, 10 | semanticdbVersion := scalafixSemanticdb.revision, 11 | maxErrors := 3 12 | ) 13 | 14 | crossScalaVersions := Seq("2.12.10") 15 | // Library default versions 16 | val defaultVersions = Map( 17 | "chisel3" -> "3.4.3", 18 | "chisel-iotesters" -> "1.5.3", 19 | "chiseltest" -> "0.3.3", 20 | "scalatest" -> "3.2.7", 21 | "organize-imports" -> "0.5.0" 22 | ) 23 | // Import libraries 24 | libraryDependencies ++= Seq("chisel3", "chisel-iotesters", "chiseltest").map { dep: String => 25 | "edu.berkeley.cs" %% dep % sys.props 26 | .getOrElse(dep + "Version", defaultVersions(dep)) 27 | } 28 | libraryDependencies += "org.scalatest" %% "scalatest" % defaultVersions("scalatest") 29 | ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % defaultVersions("organize-imports") 30 | 31 | // Aliases 32 | addCommandAlias("com", "all compile test:compile") 33 | addCommandAlias("rel", "reload") 34 | addCommandAlias("fix", "all compile:scalafix test:scalafix") 35 | addCommandAlias("fmt", "all scalafmtSbt scalafmtAll") 36 | 37 | resolvers ++= Seq( 38 | Resolver.sonatypeRepo("snapshots"), 39 | Resolver.sonatypeRepo("releases") 40 | ) 41 | 42 | scalacOptions ++= Seq( 43 | "-unchecked", 44 | "-deprecation", 45 | "-language:reflectiveCalls", 46 | "-feature", 47 | "-Xfatal-warnings", 48 | "-Ywarn-value-discard", 49 | "-Ywarn-dead-code", 50 | "-Ywarn-unused", 51 | "-Xsource:2.11" 52 | ) 53 | -------------------------------------------------------------------------------- /build.sc: -------------------------------------------------------------------------------- 1 | import mill._ 2 | import mill.scalalib._ 3 | import scalafmt._ 4 | import coursier.MavenRepository 5 | import $ivy.`com.goyeau::mill-scalafix:0.2.1` 6 | import com.goyeau.mill.scalafix.ScalafixModule 7 | 8 | def mainClass = Some("Toplevel") 9 | 10 | val defaultVersions = Map( 11 | "scala" -> "2.12.13", 12 | "chisel3" -> "3.4.3", 13 | "chisel-iotesters" -> "1.5.3", 14 | "chiseltest" -> "0.3.3", 15 | "scalatest" -> "3.2.7", 16 | "organize-imports" -> "0.5.0", 17 | "semanticdb-scalac" -> "4.4.12" 18 | ) 19 | val binCrossScalaVersions = Seq("2.12.10") 20 | 21 | trait HasChisel3 extends ScalaModule { 22 | override def ivyDeps = Agg( 23 | ivy"edu.berkeley.cs::chisel3:${defaultVersions("chisel3")}" 24 | ) 25 | } 26 | 27 | trait HasChiselTests extends CrossSbtModule { 28 | object test extends Tests { 29 | override def ivyDeps = Agg( 30 | ivy"org.scalatest::scalatest:${defaultVersions("scalatest")}", 31 | ivy"edu.berkeley.cs::chisel-iotesters:${defaultVersions("chisel-iotesters")}", 32 | ivy"edu.berkeley.cs::chiseltest:${defaultVersions("chiseltest")}" 33 | ) 34 | def repositories = super.repositories ++ Seq( 35 | MavenRepository("https://oss.sonatype.org/content/repositories/snapshots") 36 | ) 37 | def testFrameworks = Seq("org.scalatest.tools.Framework") 38 | 39 | def testOne(args: String*) = T.command { 40 | super.runMain("org.scalatest.run", args: _*) 41 | } 42 | } 43 | } 44 | 45 | trait CodeQuality extends ScalafixModule with ScalafmtModule { 46 | def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:${defaultVersions("organize-imports")}") 47 | // Override semanticdb version due to unavailable 4.4.0 for Scala 2.12.13. 48 | override def scalacPluginIvyDeps = 49 | Agg(ivy"org.scalameta:::semanticdb-scalac:${defaultVersions("semanticdb-scalac")}") 50 | } 51 | 52 | trait Aliases extends Module { 53 | def fmt() = T.command { 54 | toplevel.reformat() 55 | toplevel.fix() 56 | } 57 | } 58 | 59 | trait ScalacOptions extends ScalaModule { 60 | override def scalacOptions = T { 61 | super.scalacOptions() ++ Seq( 62 | "-language:reflectiveCalls", 63 | "-feature", 64 | "-Xfatal-warnings", 65 | "-Ywarn-value-discard", 66 | "-Ywarn-dead-code", 67 | "-Ywarn-unused", 68 | "-Xsource:2.11" 69 | ) 70 | } 71 | } 72 | 73 | object toplevel 74 | extends CrossSbtModule 75 | with HasChisel3 76 | with HasChiselTests 77 | with CodeQuality 78 | with Aliases 79 | with ScalacOptions { 80 | override def millSourcePath = super.millSourcePath 81 | def crossScalaVersion = defaultVersions("scala") 82 | } 83 | -------------------------------------------------------------------------------- /constraints/ecp5-ulx3s.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "io_clock" SITE "G2"; 2 | IOBUF PORT "io_clock" PULLMODE=NONE IO_TYPE=LVCMOS33; 3 | FREQUENCY PORT "io_clock" 25 MHZ; 4 | 5 | #LOCATE COMP "reset" SITE "D6"; 6 | #IOBUF PORT "reset" PULLMODE=UP IO_TYPE=LVCMOS33; 7 | 8 | LOCATE COMP "io_tx" SITE "L4"; 9 | LOCATE COMP "io_rx" SITE "M1"; 10 | 11 | IOBUF PORT "io_tx" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4; 12 | IOBUF PORT "io_rx" PULLMODE=UP IO_TYPE=LVCMOS33; 13 | 14 | LOCATE COMP "io_led0" SITE "B2"; 15 | LOCATE COMP "io_led1" SITE "C2"; 16 | LOCATE COMP "io_led2" SITE "C1"; 17 | LOCATE COMP "io_led3" SITE "D2"; 18 | LOCATE COMP "io_led4" SITE "D1"; 19 | LOCATE COMP "io_led5" SITE "E2"; 20 | LOCATE COMP "io_led6" SITE "E1"; 21 | LOCATE COMP "io_led7" SITE "H3"; 22 | IOBUF PORT "io_led0" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 23 | IOBUF PORT "io_led1" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 24 | IOBUF PORT "io_led2" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 25 | IOBUF PORT "io_led3" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 26 | IOBUF PORT "io_led4" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 27 | IOBUF PORT "io_led5" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 28 | IOBUF PORT "io_led6" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; 29 | IOBUF PORT "io_led7" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; -------------------------------------------------------------------------------- /constraints/storey_peak_stratixV.sdc: -------------------------------------------------------------------------------- 1 | # Main system clock (125 Mhz) 2 | create_clock -name "io_clock" -period 8.000ns [get_ports {io_clock}] 3 | 4 | # Automatically constrain PLL and other generated clocks 5 | derive_pll_clocks -create_base_clocks 6 | 7 | # Automatically calculate clock uncertainty to jitter and other effects. 8 | derive_clock_uncertainty 9 | -------------------------------------------------------------------------------- /constraints/storey_peak_stratixV.tcl: -------------------------------------------------------------------------------- 1 | # Global 2 | set_global_assignment -name RESERVE_ALL_UNUSED_PINS_WEAK_PULLUP "As output driving ground" 3 | 4 | # Clock 5 | set_location_assignment PIN_M23 -to io_clock 6 | set_instance_assignment -name IO_STANDARD "SSTL-135" -to io_clock 7 | 8 | # Reset 9 | 10 | # LEDs 11 | set_location_assignment PIN_A11 -to io_led0 12 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_led0 13 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_led0 14 | set_instance_assignment -name SLEW_RATE 0 -to io_led0 15 | 16 | # Pins to be scanned 17 | set_location_assignment PIN_A10 -to io_A10 18 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_A10 19 | set_instance_assignment -name SLEW_RATE 0 -to io_A10 20 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_A10 21 | 22 | set_location_assignment PIN_B10 -to io_B10 23 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_B10 24 | set_instance_assignment -name SLEW_RATE 0 -to io_B10 25 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_B10 26 | 27 | set_location_assignment PIN_AW26 -to io_AW26 28 | set_location_assignment PIN_AV26 -to io_AV26 29 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_AW26 30 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_AV26 31 | set_instance_assignment -name SLEW_RATE 0 -to io_AW26 32 | set_instance_assignment -name SLEW_RATE 0 -to io_AV26 33 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_AW26 34 | set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to io_AV26 -------------------------------------------------------------------------------- /img/Diagram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosedp/chisel-fpga-pinfinder/455cbac37cf83b37fb174c8d7100c170ecec85f0/img/Diagram1.png -------------------------------------------------------------------------------- /img/Terminal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosedp/chisel-fpga-pinfinder/455cbac37cf83b37fb174c8d7100c170ecec85f0/img/Terminal1.png -------------------------------------------------------------------------------- /pinfinder.core: -------------------------------------------------------------------------------- 1 | CAPI=2: 2 | 3 | name: carlosedp:demo:pinfinder:0 4 | 5 | filesets: 6 | ulx3s-85: 7 | depend: ["fusesoc:utils:generators:0.1.6"] 8 | files: 9 | - constraints/ecp5-ulx3s.lpf: { file_type: LPF } 10 | 11 | storey_peak_stratixV: 12 | depend: ["fusesoc:utils:generators:0.1.6"] 13 | files: 14 | - constraints/storey_peak_stratixV.sdc: { file_type: SDC } 15 | - constraints/storey_peak_stratixV.tcl: { file_type: tclSource } 16 | 17 | generate: 18 | ulx3s: 19 | generator: chisel 20 | parameters: 21 | # extraargs: "-board ulx3s" 22 | chiselproject: toplevel 23 | copy_core: true 24 | output: 25 | files: 26 | - generated/Toplevel.v: { file_type: verilogSource } 27 | 28 | storey_peak_stratixV: 29 | generator: chisel 30 | parameters: 31 | # extraargs: "-board storey_peak_stratixV" 32 | chiselproject: toplevel 33 | copy_core: true 34 | output: 35 | files: 36 | - generated/Toplevel.v: { file_type: verilogSource } 37 | 38 | targets: 39 | ulx3s_85: 40 | default_tool: trellis 41 | description: ULX3S 85k version 42 | filesets: [ulx3s-85] 43 | generate: [ulx3s] 44 | tools: 45 | trellis: 46 | nextpnr_options: [--package, CABGA381, --85k] 47 | toplevel: Toplevel 48 | 49 | storey_peak_stratixV: 50 | default_tool : quartus 51 | description: Microsoft Storey Peak (Catapult) Stratix V FPGA Accelerator 52 | filesets : [storey_peak_stratixV] 53 | generate : [storey_peak_stratixV] 54 | tools: 55 | quartus: 56 | family : Stratix V 57 | device : 5SGSMD5K1F40C2 58 | toplevel: Toplevel 59 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.9 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") 3 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.17") 4 | -------------------------------------------------------------------------------- /scripts/mill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This is a wrapper script, that automatically download mill from GitHub release pages 4 | # You can give the required mill version with MILL_VERSION env variable 5 | # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION 6 | DEFAULT_MILL_VERSION=0.9.6 7 | 8 | set -e 9 | 10 | if [ -z "$MILL_VERSION" ] ; then 11 | if [ -f ".mill-version" ] ; then 12 | MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" 13 | elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then 14 | MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) 15 | else 16 | MILL_VERSION=$DEFAULT_MILL_VERSION 17 | fi 18 | fi 19 | 20 | if [ "x${XDG_CACHE_HOME}" != "x" ] ; then 21 | MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" 22 | else 23 | MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" 24 | fi 25 | MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" 26 | 27 | version_remainder="$MILL_VERSION" 28 | MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" 29 | MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" 30 | 31 | if [ ! -x "$MILL_EXEC_PATH" ] ; then 32 | mkdir -p $MILL_DOWNLOAD_PATH 33 | if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then 34 | ASSEMBLY="-assembly" 35 | fi 36 | DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download 37 | MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION${ASSEMBLY}" 38 | curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" 39 | chmod +x "$DOWNLOAD_FILE" 40 | mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" 41 | unset DOWNLOAD_FILE 42 | unset MILL_DOWNLOAD_URL 43 | fi 44 | 45 | unset MILL_DOWNLOAD_PATH 46 | unset MILL_VERSION 47 | 48 | exec $MILL_EXEC_PATH "$@" 49 | -------------------------------------------------------------------------------- /src/main/scala/Toplevel.scala: -------------------------------------------------------------------------------- 1 | import chisel3._ 2 | import chisel3.util._ 3 | import chisel3.experimental._ 4 | import firrtl.annotations.PresetAnnotation 5 | 6 | // The Main object extending App to generate the Verilog code. 7 | object Toplevel extends App { 8 | 9 | // Change the `pinsToTest` variable to test different pins. 10 | // Do not change the rest of the code. 11 | // Define pins to test. Must match the pin name from the constraints file for your board. 12 | val pinsToTest = Seq("A10", "B10", "AW26", "AV26") 13 | 14 | // Define the frequency of the clock signal in Hz for the board you are using. 15 | // For ULX3S, use 25000000 16 | // For StoreyPeak, use 125000000 17 | val frequency = 125000000 18 | 19 | // ----------------------------------------------------------------- 20 | // Do not change the code below this line 21 | // ----------------------------------------------------------------- 22 | 23 | // Generate Verilog 24 | (new chisel3.stage.ChiselStage).emitVerilog( 25 | new Toplevel(frequency), // For StoreyPeak 26 | args 27 | ) 28 | } 29 | 30 | // Toplevel (use RawModule to define our own Clock and Reset) 31 | class Toplevel(frequency: Int, pinsToTest: Seq[String], baudRate: Int = 115200) extends RawModule { 32 | val io = IO(new Bundle { 33 | val clock = Input(Clock()) 34 | val led0 = Output(Bool()) 35 | val pins = pinsToTest.map { pin => 36 | pin -> Output(UInt(1.W)) 37 | }.toMap 38 | }) 39 | 40 | // Initialize registers to their reset value when the bitstream is programmed since there is no reset wire 41 | val reset = IO(Input(AsyncReset())) 42 | annotate(new ChiselAnnotation { 43 | override def toFirrtl = PresetAnnotation(reset.toTarget) 44 | }) 45 | 46 | chisel3.withClockAndReset(io.clock, reset) { 47 | // Send message to pins 48 | pinsToTest.foreach { pin => 49 | new PinFind(pin + " ", io.pins(pin), frequency, baudRate) 50 | } 51 | 52 | // Heartbeat led 53 | val led = RegInit(0.U(1.W)) 54 | val (_, counterWrap) = Counter(true.B, frequency / 2) 55 | when(counterWrap) { 56 | led := ~led 57 | } 58 | io.led0 := led 59 | } 60 | } 61 | 62 | class PinFind(msg: String, output: UInt, frequency: Int, baudRate: Int) { 63 | val text = VecInit(msg.map(_.U)) 64 | val len = msg.length.U 65 | 66 | val tx = Module(new uart.BufferedTx(frequency, baudRate)) 67 | output := tx.io.txd 68 | val cntReg = RegInit(0.U(8.W)) 69 | tx.io.channel.bits := text(cntReg) 70 | tx.io.channel.valid := cntReg =/= len 71 | 72 | when(tx.io.channel.ready && cntReg =/= len) { 73 | cntReg := cntReg + 1.U 74 | } 75 | val (_, counterWrap) = Counter(true.B, frequency / 2) 76 | when(counterWrap) { 77 | cntReg := 0.U 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/Uart.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * A UART is a serial port, also called an RS232 interface. 4 | * This file was copied from Martin Schoeberl examples repository at: 5 | * https://github.com/schoeberl/chisel-examples/blob/master/src/main/scala/uart/Uart.scala 6 | * 7 | */ 8 | 9 | package uart 10 | 11 | import chisel3._ 12 | import chisel3.util._ 13 | 14 | class UartIO extends DecoupledIO(UInt(8.W)) { 15 | override def cloneType: this.type = new UartIO().asInstanceOf[this.type] 16 | } 17 | 18 | /** 19 | * Transmit part of the UART. 20 | * A minimal version without any additional buffering. 21 | * Use a ready/valid handshaking. 22 | */ 23 | class Tx(frequency: Int, baudRate: Int) extends Module { 24 | val io = IO(new Bundle { 25 | val txd = Output(UInt(1.W)) 26 | val channel = Flipped(new UartIO()) 27 | }) 28 | 29 | val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).asUInt() 30 | 31 | val shiftReg = RegInit(0x7ff.U) 32 | val cntReg = RegInit(0.U(20.W)) 33 | val bitsReg = RegInit(0.U(4.W)) 34 | 35 | io.channel.ready := (cntReg === 0.U) && (bitsReg === 0.U) 36 | io.txd := shiftReg(0) 37 | 38 | when(cntReg === 0.U) { 39 | 40 | cntReg := BIT_CNT 41 | when(bitsReg =/= 0.U) { 42 | val shift = shiftReg >> 1 43 | shiftReg := Cat(1.U, shift(9, 0)) 44 | bitsReg := bitsReg - 1.U 45 | }.otherwise { 46 | when(io.channel.valid) { 47 | shiftReg := Cat(Cat(3.U, io.channel.bits), 0.U) // two stop bits, data, one start bit 48 | bitsReg := 11.U 49 | }.otherwise { 50 | shiftReg := 0x7ff.U 51 | } 52 | } 53 | 54 | }.otherwise { 55 | cntReg := cntReg - 1.U 56 | } 57 | } 58 | 59 | /** 60 | * Receive part of the UART. 61 | * A minimal version without any additional buffering. 62 | * Use a ready/valid handshaking. 63 | * 64 | * The following code is inspired by Tommy's receive code at: 65 | * https://github.com/tommythorn/yarvi 66 | */ 67 | class Rx(frequency: Int, baudRate: Int) extends Module { 68 | val io = IO(new Bundle { 69 | val rxd = Input(UInt(1.W)) 70 | val channel = new UartIO() 71 | }) 72 | 73 | val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).U 74 | val START_CNT = ((3 * frequency / 2 + baudRate / 2) / baudRate - 1).U 75 | 76 | // Sync in the asynchronous RX data, reset to 1 to not start reading after a reset 77 | val rxReg = RegNext(RegNext(io.rxd, 1.U), 1.U) 78 | 79 | val shiftReg = RegInit(0.U(8.W)) 80 | val cntReg = RegInit(0.U(20.W)) 81 | val bitsReg = RegInit(0.U(4.W)) 82 | val valReg = RegInit(false.B) 83 | 84 | when(cntReg =/= 0.U) { 85 | cntReg := cntReg - 1.U 86 | }.elsewhen(bitsReg =/= 0.U) { 87 | cntReg := BIT_CNT 88 | shiftReg := Cat(rxReg, shiftReg >> 1) 89 | bitsReg := bitsReg - 1.U 90 | // the last shifted in 91 | when(bitsReg === 1.U) { 92 | valReg := true.B 93 | } 94 | }.elsewhen(rxReg === 0.U) { // wait 1.5 bits after falling edge of start 95 | cntReg := START_CNT 96 | bitsReg := 8.U 97 | } 98 | 99 | when(valReg && io.channel.ready) { 100 | valReg := false.B 101 | } 102 | 103 | io.channel.bits := shiftReg 104 | io.channel.valid := valReg 105 | } 106 | 107 | /** 108 | * A single byte buffer with a ready/valid interface 109 | */ 110 | class Buffer extends Module { 111 | val io = IO(new Bundle { 112 | val in = Flipped(new UartIO()) 113 | val out = new UartIO() 114 | }) 115 | 116 | val empty :: full :: Nil = Enum(2) 117 | val stateReg = RegInit(empty) 118 | val dataReg = RegInit(0.U(8.W)) 119 | 120 | io.in.ready := stateReg === empty 121 | io.out.valid := stateReg === full 122 | 123 | when(stateReg === empty) { 124 | when(io.in.valid) { 125 | dataReg := io.in.bits 126 | stateReg := full 127 | } 128 | }.otherwise { // full 129 | when(io.out.ready) { 130 | stateReg := empty 131 | } 132 | } 133 | io.out.bits := dataReg 134 | } 135 | 136 | /** 137 | * A transmitter with a single buffer. 138 | */ 139 | class BufferedTx(frequency: Int, baudRate: Int) extends Module { 140 | val io = IO(new Bundle { 141 | val txd = Output(UInt(1.W)) 142 | val channel = Flipped(new UartIO()) 143 | }) 144 | val tx = Module(new Tx(frequency, baudRate)) 145 | val buf = Module(new Buffer()) 146 | 147 | buf.io.in <> io.channel 148 | tx.io.channel <> buf.io.out 149 | io.txd <> tx.io.txd 150 | } 151 | 152 | /** 153 | * Send a string. 154 | */ 155 | class Sender(frequency: Int, baudRate: Int, msg: String = "Hello World!") extends Module { 156 | val io = IO(new Bundle { 157 | val txd = Output(UInt(1.W)) 158 | }) 159 | 160 | val tx = Module(new BufferedTx(frequency, baudRate)) 161 | 162 | io.txd := tx.io.txd 163 | 164 | val text = VecInit(msg.map(_.U)) 165 | val len = msg.length.U 166 | 167 | val cntReg = RegInit(0.U(8.W)) 168 | 169 | tx.io.channel.bits := text(cntReg) 170 | tx.io.channel.valid := cntReg =/= len 171 | 172 | when(tx.io.channel.ready && cntReg =/= len) { 173 | cntReg := cntReg + 1.U 174 | } 175 | } 176 | 177 | class Echo(frequency: Int, baudRate: Int) extends Module { 178 | val io = IO(new Bundle { 179 | val txd = Output(UInt(1.W)) 180 | val rxd = Input(UInt(1.W)) 181 | }) 182 | // io.txd := RegNext(io.rxd) 183 | val tx = Module(new BufferedTx(frequency, baudRate)) 184 | val rx = Module(new Rx(frequency, baudRate)) 185 | io.txd := tx.io.txd 186 | rx.io.rxd := io.rxd 187 | tx.io.channel <> rx.io.channel 188 | } 189 | 190 | class UartMain(frequency: Int, baudRate: Int) extends Module { 191 | val io = IO(new Bundle { 192 | val rxd = Input(UInt(1.W)) 193 | val txd = Output(UInt(1.W)) 194 | }) 195 | 196 | val doSender = true 197 | 198 | if (doSender) { 199 | val s = Module(new Sender(frequency, baudRate)) 200 | io.txd := s.io.txd 201 | } else { 202 | val e = Module(new Echo(frequency, baudRate)) 203 | e.io.rxd := io.rxd 204 | io.txd := e.io.txd 205 | } 206 | 207 | } 208 | 209 | // object UartMain extends App { 210 | // (new chisel3.stage.ChiselStage).emitVerilog(new UartMain(50000000, 115200), Array("--target-dir", "generated")) 211 | // } 212 | -------------------------------------------------------------------------------- /src/test/scala/BlinkySpec.scala: -------------------------------------------------------------------------------- 1 | import chisel3.iotesters._ 2 | import org.scalatest.flatspec._ 3 | import org.scalatest.matchers.should._ 4 | 5 | class BlinkySpec extends AnyFlatSpec with Matchers { 6 | "Blinky" should "pass" in { 7 | chisel3.iotesters.Driver(() => new Blinky(25000)) { c => 8 | new PeekPokeTester(c) { 9 | 10 | println("Start the blinking LED") 11 | for (_ <- 0 until 25) { 12 | step(10000) 13 | val l0 = if (peek(c.io.led0) == 0) 'o' else '*' 14 | val l1 = if (peek(c.io.led1) == 0) 'o' else '*' 15 | val l2 = if (peek(c.io.led2) == 0) 'o' else '*' 16 | printf("Leds: " + l0 + " " + l1 + " " + l2 + "\r") 17 | } 18 | println("\nEnd the blinking LED") 19 | } 20 | } should be(true) 21 | } 22 | } 23 | 24 | // Verilator sim 25 | class VerilatorSpec extends AnyFlatSpec with Matchers { 26 | "VerilatorSim" should "pass" in { 27 | Driver.execute( 28 | Array("--backend-name", "verilator"), 29 | () => new Blinky(1) 30 | ) { c => 31 | new WaveformCounterTester(c, 25) 32 | } should be(true) 33 | } 34 | } 35 | 36 | // Generate VCD output 37 | class WaveformCounterSpec extends AnyFlatSpec with Matchers { 38 | "WaveformCounter" should "pass" in { 39 | Driver.execute( 40 | Array("--generate-vcd-output", "on"), 41 | () => new Blinky(1) 42 | ) { c => 43 | new WaveformCounterTester(c, 25) 44 | } should be(true) 45 | } 46 | } 47 | 48 | class WaveformCounterTester(dut: Blinky, cycles: Int) 49 | extends PeekPokeTester(dut) { 50 | for (_ <- 0 until cycles) { 51 | step(1) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/UtilitiesSpec.scala: -------------------------------------------------------------------------------- 1 | import scalautils.ParseArguments 2 | 3 | import org.scalatest.flatspec._ 4 | import org.scalatest.matchers.should._ 5 | 6 | class ParseArgumentsSpec extends AnyFlatSpec with Matchers { 7 | 8 | "ParseArguments" should "parse one parameter" in { 9 | val args = Array("-param1", "data1", "generated", "singleparam1") 10 | val (params, remainingargs) = ParseArguments(args, List("param1", "param2")) 11 | 12 | assert(params === Map("param1" -> "data1")) 13 | assert(remainingargs === List("generated", "singleparam1")) 14 | } 15 | 16 | "ParseArguments" should "parse multiple parameters" in { 17 | val args = Array("-param1", "data1", "generated", "-param2", "data2", "-anotherparam") 18 | val (params, remainingargs) = ParseArguments(args, List("param1", "param2")) 19 | 20 | assert(params === Map("param1" -> "data1", "param2" -> "data2")) 21 | assert(remainingargs === List("generated", "-anotherparam")) 22 | } 23 | 24 | "ParseArguments" should "parse one parameter and ignore a wrong one" in { 25 | val args = Array("-param1", "-wrong", "generated", "-anotherparam", "mydata", "-param2", "data2") 26 | val (params, remainingargs) = ParseArguments(args, List("param1", "param2")) 27 | 28 | assert(params === Map("param2" -> "data2")) 29 | assert(remainingargs === List("-wrong", "generated", "-anotherparam", "mydata")) 30 | } 31 | 32 | "ParseArguments" should "parse one parameter with double dash('--')" in { 33 | val args = Array("--param1", "data1", "generated", "--anotherparam", "mydata", "-param2", "data2") 34 | val (params, remainingargs) = ParseArguments(args, List("param1", "param2"), "--") 35 | 36 | assert(params === Map("param1" -> "data1")) 37 | assert(remainingargs === List("generated", "--anotherparam", "mydata", "-param2", "data2")) 38 | } 39 | 40 | "ParseArguments" should "parse one parameter with single dash and pass double dash ones" in { 41 | val args = Array("-param1", "data1", "generated", "--anotherparam", "mydata", "-param2", "data2") 42 | val (params, remainingargs) = ParseArguments(args, List("param1"), "-") 43 | 44 | assert(params === Map("param1" -> "data1")) 45 | assert(remainingargs === List("generated", "--anotherparam", "mydata", "-param2", "data2")) 46 | } 47 | 48 | } 49 | --------------------------------------------------------------------------------