From 4642bafae1e73d90078930f13cc66d1062c26a43 Mon Sep 17 00:00:00 2001 From: xenia Date: Sat, 16 Mar 2024 23:41:51 +0100 Subject: [PATCH] FPGA flake --- flake.nix | 5 +++ fpga/.envrc | 1 + fpga/.gitignore | 3 ++ fpga/flake.lock | 59 ++++++++++++++++++++++++++ fpga/flake.nix | 80 ++++++++++++++++++++++++++++++++++++ fpga/pins.pcf | 2 + fpga/simulation/test_led.cpp | 56 +++++++++++++++++++++++++ fpga/src/top.v | 43 +++++++++++++++++++ fpga/verilator.nix | 47 +++++++++++++++++++++ 9 files changed, 296 insertions(+) create mode 100644 fpga/.envrc create mode 100644 fpga/.gitignore create mode 100644 fpga/flake.lock create mode 100644 fpga/flake.nix create mode 100644 fpga/pins.pcf create mode 100644 fpga/simulation/test_led.cpp create mode 100644 fpga/src/top.v create mode 100644 fpga/verilator.nix diff --git a/flake.nix b/flake.nix index ae75542..e350372 100644 --- a/flake.nix +++ b/flake.nix @@ -27,5 +27,10 @@ description = "Minimal python flake"; welcomeText = "hisss 🐍🐍🐍"; }; + templates.verilog= { + path = ./fpga; + description = "FPGA flake, running verilog on ice40"; + welcomeText = "mjau 🧊"; + }; }; } diff --git a/fpga/.envrc b/fpga/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/fpga/.envrc @@ -0,0 +1 @@ +use flake diff --git a/fpga/.gitignore b/fpga/.gitignore new file mode 100644 index 0000000..275a371 --- /dev/null +++ b/fpga/.gitignore @@ -0,0 +1,3 @@ +abc.history +.direnv +result diff --git a/fpga/flake.lock b/fpga/flake.lock new file mode 100644 index 0000000..3e87ea1 --- /dev/null +++ b/fpga/flake.lock @@ -0,0 +1,59 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1697009197, + "narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/fpga/flake.nix b/fpga/flake.nix new file mode 100644 index 0000000..501607d --- /dev/null +++ b/fpga/flake.nix @@ -0,0 +1,80 @@ +{ + description = "FPGA flake, running verilog on ice40"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (sys: + let pkgs = nixpkgs.legacyPackages.${sys}; + + verilator = import ./verilator.nix pkgs ; + yosys = pkgs.yosys; + + vflags = ''-DSIMULATE -Wno-fatal -Wpedantic -Wwarn-lint -Wwarn-style -Wno-PINCONNECTEMPTY -Wno-BLKSEQ -CFLAGS "-Wpedantic -std=c++20"''; + + verilate-src = cmd: '' + cp -r ${./src} ./src + cp -r ${./simulation} ./simulation + find ./src/ -name '*.v' -exec ${verilator}/bin/verilator ${vflags} ${cmd} {} + + ''; + + lint = pkgs.runCommand "lint" {} '' + ${verilate-src "--lint-only"} + echo "compiler didn't get angry :3" + : 3 > $out + ''; + + test-trace = pkgs.runCommandCC "test-trace" {} '' + set -e + ${verilate-src "--cc --build --exe --trace -CFLAGS -DTRACE=1 ./simulation/test_led.cpp -top top"} + mv obj_dir "$out" && mkdir "$out/bin" + cp "$out/Vtop" "$out/bin/sim" + $out/bin/sim $out/trace.vcd + echo "${pkgs.gtkwave}/bin/gtkwave $out/trace.vcd" > $out/bin/test-trace + chmod u+x $out/bin/test-trace + ''; + + synth = pkgs.runCommandCC "synth" {} '' + mkdir -p "$out" + find ${./src} -name '*.v' -exec ${yosys}/bin/yosys -f ' -sv' -q -p "synth_ice40 -top top -json $out/synth.json" {} + + ''; + + pnr-interactive = pkgs.writeScriptBin "pnr-interactive" '' + ${pkgs.nextpnrWithGui}/bin/nextpnr-ice40 --up5k --package sg48 --pcf ${./pins.pcf} --json ${synth}/synth.json --gui + ''; + + pnr = pkgs.runCommandCC "pnr" {} '' + mkdir -p "$out" + ${pkgs.nextpnrWithGui}/bin/nextpnr-ice40 --up5k --package sg48 --pcf ${./pins.pcf} --json ${synth}/synth.json --asc "$out/pnr.asc" \ + --freq 50 + ''; + + flash = pkgs.writeScriptBin "flash" '' + set -e + bin="$(mktemp)" + ${pkgs.icestorm}/bin/icepack ${pnr}/pnr.asc "$bin" + ${pkgs.icestorm}/bin/iceprog "$bin" + ''; + + deps = [ + yosys pkgs.nextpnrWithGui pkgs.icestorm verilator pkgs.gtkwave + pkgs.picocom + ]; + in rec { + packages.verilator = verilator; + + packages.lint = lint; + + packages.test-trace = test-trace; + + packages.synth = synth; + packages.pnr-interactive = pnr-interactive; + packages.pnr = pnr; + packages.flash = flash; + + devShells.default = pkgs.mkShell { packages = deps; }; + } + ); +} diff --git a/fpga/pins.pcf b/fpga/pins.pcf new file mode 100644 index 0000000..b768878 --- /dev/null +++ b/fpga/pins.pcf @@ -0,0 +1,2 @@ +set_io clk_12m 35 +set_io led_red 26 diff --git a/fpga/simulation/test_led.cpp b/fpga/simulation/test_led.cpp new file mode 100644 index 0000000..d1c68ab --- /dev/null +++ b/fpga/simulation/test_led.cpp @@ -0,0 +1,56 @@ +#include "Vtop.h" +#include "verilated.h" + +#include +#include +#include + +#include "verilated_vcd_c.h" + +struct state { + VerilatedContext *ctx; + Vtop *vtop; + VerilatedVcdC *trace; +}; + +void posedge(state &state) { + state.ctx->timeInc(1); + state.vtop->clk_12m = 1; + state.vtop->eval(); + state.trace->dump(state.ctx->time()); + + state.ctx->timeInc(1); + state.vtop->clk_12m = 0; + state.vtop->eval(); + state.trace->dump(state.ctx->time()); +} + +int main(int argc, char **argv) { + VerilatedContext *vctx = new VerilatedContext; + Verilated::traceEverOn(true); + Vtop *vtop = new Vtop(vctx); + + if (argc != 2) { + std::cout << "Run with argument for destination!" << std::endl; + return 1; + } + + VerilatedVcdC *trace = new VerilatedVcdC; + vtop->trace(trace, 99); + trace->open(argv[1]); + std::cout << "(writing trace to " << argv[1] << ")" << std::endl; + + state state = { + .ctx = vctx, + .vtop = vtop, + .trace = trace, + }; + + + for (int i = 0; i < 2<<24; i++) { + posedge(state); + } + + state.trace->close(); +} + diff --git a/fpga/src/top.v b/fpga/src/top.v new file mode 100644 index 0000000..a975004 --- /dev/null +++ b/fpga/src/top.v @@ -0,0 +1,43 @@ +module top( + input clk_12m, + + output led_red +); + +// want clock to blink at a rate of 1Hz +// intermediate clock at 2^24 Hz = 16.77MHz +// 12MHz * 7/5 = 16.8MHz, within 0.2% + +wire clk_16_8m; +wire clk_stable; +// set ethernet clock to 50 MHz +`ifndef SIMULATE +SB_PLL40_PAD #( + .FEEDBACK_PATH("SIMPLE"), + .DIVR(4'd4), // divide by 4+1=5 + .DIVF(7'd6), // multiply by 6+1=7 + .DIVQ(3'd0), // divide by 2^0 + .FILTER_RANGE(3'b001) +) SB_PLL40_PAD_inst ( + .PACKAGEPIN(clk_12m), + .PLLOUTGLOBAL(clk_16_8m), + .RESETB(1'b1), + .BYPASS(1'b0), + .LOCK(clk_stable) +); +`else +// in simulation, tie clocks together +assign clk_16_8m = clk_12m; +assign clk_stable = 1; +`endif + +// divide 16.8MHz clock by 2^24 +reg [24:0] clk_ctr; + +always @(posedge clk_16_8m) begin + clk_ctr <= clk_ctr + 1; +end + +assign led_red = clk_stable & clk_ctr[24]; + +endmodule diff --git a/fpga/verilator.nix b/fpga/verilator.nix new file mode 100644 index 0000000..37dd864 --- /dev/null +++ b/fpga/verilator.nix @@ -0,0 +1,47 @@ +pkgs: with pkgs; + +# From https://github.com/NixOS/nixpkgs/blob/nixos-23.11/pkgs/applications/science/electronics/verilator/default.nix +# Patches out SystemC-support, as the SystemC does not build on Darwin +stdenv.mkDerivation rec { + pname = "verilator"; + version = "5.018"; + + src = fetchFromGitHub { + owner = pname; + repo = pname; + rev = "v${version}"; + hash = "sha256-f06UzNw2MQ5me03EPrVFhkwxKum/GLDzQbDNTBsJMJs="; + }; + + enableParallelBuilding = true; + buildInputs = [ perl python3 ]; # ccache + nativeBuildInputs = [ makeWrapper flex bison autoconf help2man git ]; + nativeCheckInputs = [ which numactl ]; # cmake + + doCheck = stdenv.isLinux; # darwin tests are broken for now... + checkTarget = "test"; + + preConfigure = "autoconf"; + + postPatch = '' + patchShebangs bin/* src/* nodist/* docs/bin/* examples/xml_py/* \ + test_regress/{driver.pl,t/*.{pl,pf}} \ + ci/* ci/docker/run/* ci/docker/run/hooks/* ci/docker/buildenv/build.sh + ''; + # grep '^#!/' -R . | grep -v /nix/store | less + # (in nix-shell after patchPhase) + + postInstall = lib.optionalString stdenv.isLinux '' + for x in $(ls $out/bin/verilator*); do + wrapProgram "$x" --set LOCALE_ARCHIVE "${glibcLocales}/lib/locale/locale-archive" + done + ''; + + meta = with lib; { + description = "Fast and robust (System)Verilog simulator/compiler and linter"; + homepage = "https://www.veripool.org/verilator"; + license = with licenses; [ lgpl3Only artistic2 ]; + platforms = platforms.unix; + maintainers = with maintainers; [ thoughtpolice amiloradovsky ]; + }; +}