├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── gallery ├── checkerboard.gif └── fractal.png ├── lint.vlt ├── rtl ├── image.sv ├── top.sv └── video_timer.sv ├── tangnano ├── Gowin_rPLL.v ├── pins.cst └── tangnano.sv ├── tb ├── requirements.txt ├── to_png.py └── top.tb.sv ├── top.core └── usage └── post.tcl /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.conf 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "mshr-h.veriloghdl" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // icarus 3 | "verilog.linting.linter": "iverilog", 4 | "verilog.linting.iverilog.arguments": "-g2012 -Wall -DSIM -y rtl -Y .sv", 5 | 6 | // // verilator 7 | // "verilog.linting.linter": "verilator", 8 | // "verilog.linting.verilator.arguments": "lint.vlt -Wall -DSIM -y rtl", 9 | // "verilog.linting.verilator.useWSL": true, // remove if not using WSL 10 | 11 | // fusesoc 12 | "files.associations": { 13 | "*.core": "yaml" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # List all Makefile targets which don't yield any file output 2 | .PHONY: run img init usage tangnano view lint load_tangnano clean 3 | 4 | # Assemble lists of all relevant source files 5 | RTL = $(shell find rtl -type f) 6 | CORE = top.core 7 | SRC = ${RTL} ${CORE} 8 | 9 | # Assemble lists of all relevant testbench files and the location of output files 10 | # created by the testbench 11 | TB = $(shell find tb -type f) 12 | FST = build/ucsbieee__fpga_screensaver_1.0.0/tb-icarus/dump.fst 13 | IMG = build/ucsbieee__fpga_screensaver_1.0.0/tb-icarus/image.png 14 | 15 | # Assemble a list of all hardware utilization report files and the location of 16 | # the output report file 17 | USAGE = $(shell find usage -type f) 18 | USAGE_REPORT = build/ucsbieee__fpga_screensaver_1.0.0/usage-yosys/usage.txt 19 | 20 | # Assemble a list of all Tang Nano board files and the location of the synthesized 21 | # bitstream for upload 22 | TANGNANO = $(shell find tangnano -type f) 23 | FS = build/ucsbieee__fpga_screensaver_1.0.0/tangnano-apicula/ucsbieee__fpga_screensaver_1.0.0.fs 24 | 25 | 26 | # Define primary Makefile targets (as specified in README.md) 27 | run: ${FST} 28 | img: ${IMG} 29 | init: fusesoc.conf 30 | usage: ${USAGE_REPORT} 31 | tangnano: ${FS} 32 | 33 | # Open the timing diagram visualization 34 | view: fusesoc.conf ${FST} 35 | gtkwave ${FST} > /dev/null 2>&1 & 36 | 37 | # Perform the linting procedure 38 | lint: fusesoc.conf ${SRC} 39 | fusesoc run --target lint ucsbieee::fpga_screensaver 40 | 41 | # Synthesize the FPGA design into a bistream for upload to the Tang Nano 42 | ${FS}: fusesoc.conf ${SRC} ${TANGNANO} 43 | fusesoc run --target tangnano ucsbieee::fpga_screensaver 44 | 45 | # Load the Tang Nano with the synthesized bistream 46 | load_tangnano: ${FS} 47 | sudo openFPGALoader -m -b tangnano $< 48 | 49 | # Configure the project folder as a FuseSoC core library 50 | fusesoc.conf: 51 | fusesoc library add tests . --sync-type=local 52 | 53 | # Remove all of the Makefile-created build files and start fresh 54 | clean: 55 | rm -rf build fusesoc.conf 56 | 57 | # Simulate the RTL 58 | ${FST}: fusesoc.conf ${SRC} ${TB} 59 | fusesoc run --target tb ucsbieee::fpga_screensaver 60 | 61 | # Create a PNG representing the VGA's output 62 | ${IMG}: fusesoc.conf ${SRC} ${TB} ${FST} 63 | @pip3 install -r tb/requirements.txt > /dev/null 64 | python3 tb/to_png.py $(IMG:.png=.txt) ${IMG} 65 | 66 | # Statically-analyze (without uploading/running it) the design's FPGA logic 67 | # cell utilization 68 | ${USAGE_REPORT}: fusesoc.conf ${SRC} ${USAGE} 69 | fusesoc run --target usage ucsbieee::fpga_screensaver 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Screensaver on an FPGA 3 | 4 | [github.com/E4tHam/fpga_screensaver](https://github.com/E4tHam/fpga_screensaver) 5 | 6 | This project implements the VGA protocol and allows custom images to be displayed to the screen using the [Sipeed Tang Nano](https://tangnano.sipeed.com/en) FPGA dev board. 7 | 8 | This is a good introductory project to RTL (*[Register-Transfer Level code](https://vhdlwhiz.com/terminology/register-transfer-level)*), analog video interfaces, and fundamental FPGA (*[Field-Programmable Gate Array](https://www.digikey.com/en/blog/fpgas-101-a-beginners-guide)*) concepts in the [Verilog](http://en.wikipedia.org/wiki/Verilog) and [SystemVerilog](http://en.wikipedia.org/wiki/SystemVerilog) HDLs (*[Hardware Description Languages](http://en.wikipedia.org/wiki/Hardware_description_language)*). 9 | 10 | ## Gallery 11 | 12 | These are the images that are supported by this repository: 13 | 14 | ### Checkerboard 15 | 16 | ![Checkerboard](gallery/checkerboard.gif) 17 | 18 | ### Fractal 19 | 20 | ![Fractal](gallery/fractal.png) 21 | 22 | ## Workspace Setup 23 | 24 | At the time of writing, some of the required tools must be compiled from source (*e.g. `nextpnr-gowin`, `openFPGALoader`*) and `openFPGALoader` specifically requires a native Linux OS for COM/serial port acesss. 25 | 26 | The process is fairly straightforward, but the compilation takes a long time (*at least on the Raspberry Pi for which the installation procedure was validated*). The instructions are maintained on [this Notion document](https://gamy-hamburger-7fe.notion.site/FuseSoC-Gowin-Toolchain-Installation-30af2f32f31745eeb0b53ba20aac22d2). 27 | 28 | ## To Run 29 | 30 | There are several different [Makefile](https://www.gnu.org/software/make) targets specified, each of which represents an element of the project build procedure: 31 | 32 | ```bash 33 | make run # generaes the dump and VGA image file 34 | make view # opens the dump file in gtkwave 35 | make lint # ensure code meets Verilator standards 36 | make usage # report generic cell utilization 37 | make tangnano # generate tangnano bitstream 38 | make load_tangnano # load bistream to tangnano 39 | ``` 40 | 41 | The VGA output is formatted in a png here: `build/ucsbieee__fpga_screensaver_1.0.0/tb-icarus/image.png`. 42 | 43 | ## Requirements 44 | 45 | * [FuseSoC](https://fusesoc.readthedocs.io): RTL design build system, similar in concept to CMake, Bazel, etc. but specific to HDL code 46 | * [Icarus Verilog](http://iverilog.icarus.com): Verilog design simulator 47 | * [GTKWave](http://gtkwave.sourceforge.net): Waveform viewer (view the timing-diagram of the simulation) 48 | * [Verilator](https://www.veripool.org/verilator): (System)Verilog design simulator 49 | * [Yosys](https://yosyshq.net/yosys): RTL design synthesis tool, similar in concept to the "compilation" stage during C++ compilation 50 | * [`nextpnr-gowin`](https://github.com/YosysHQ/nextpnr#nextpnr-gowin): Gowin-specific FPGA "place-and-route" tool, similar in concept to the "assembly" stage during C++ compilation 51 | * [Apicula](https://github.com/YosysHQ/apicula): Gowin-specific FPGA design formatter, similar in concept to the "linker" stage during C++ compilation 52 | * [`openFPGALoader`](https://github.com/trabucayre/openFPGALoader): FPGA programming tool 53 | 54 | ## Directory Structure 55 | 56 | This project is organized so as to cleanly separate/delineate groups of files responsible for each part of the design: 57 | 58 | * `.vscode`: VSCode workspace configuration files 59 | * `build`: FuseSoC build directory (*doesn't exist until project is built for the first time*) 60 | * `rtl`: SystemVerilog code to define the VGA display driver 61 | * `tangnano`: (System)Verilog code to define elements of the project which are uniquely specific to the Sipeed Tang Nano dev board 62 | * `tb`: SystemVerilog code to define the testbench, used for verifying the design behavior in simulation before programming the physical FPGA 63 | * `usage`: Files related to generating a cell utilization report in Yosys. 64 | * `.gitignore`: Git SCM configuration to help keep the repository clean of files built by FuseSoC 65 | * `fusesoc.conf`: FuseSoC project definition/configuration 66 | * `lint.vlt`: Verilator configuration specifying rules in verifying the syntax of the RTL code 67 | * `Makefile`: GNU Make configuration specifying common project tasks/targets 68 | * `top.core`: FuseSoC "core" configuration specifiing how to build each of the (System)Verilog files in the design 69 | -------------------------------------------------------------------------------- /gallery/checkerboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sifferman/fpga_screensaver/0a6495920aa9f18d98e1bd4382b5ec73d067a650/gallery/checkerboard.gif -------------------------------------------------------------------------------- /gallery/fractal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sifferman/fpga_screensaver/0a6495920aa9f18d98e1bd4382b5ec73d067a650/gallery/fractal.png -------------------------------------------------------------------------------- /lint.vlt: -------------------------------------------------------------------------------- 1 | `verilator_config 2 | lint_off -rule UNUSED 3 | lint_off -file "*.tb.*" 4 | -------------------------------------------------------------------------------- /rtl/image.sv: -------------------------------------------------------------------------------- 1 | 2 | module image #( 3 | parameter SELECT = 0, 4 | parameter SCREEN_WIDTH = 640, 5 | parameter SCREEN_HEIGHT = 480 6 | ) ( 7 | input clk, rst, 8 | input [$clog2(SCREEN_WIDTH)-1:0] position_x, position_x_next, 9 | input [$clog2(SCREEN_HEIGHT)-1:0] position_y, position_y_next, 10 | input [31:0] frame, 11 | output logic [3:0] r, g, b 12 | ); 13 | 14 | generate 15 | // ==== Checkerboard ==== 16 | if (SELECT == 0) begin : checkerboard 17 | // 18 | 19 | wire color_next = position_x_next[6] ^ position_y_next[6]; 20 | 21 | always_ff @ (posedge clk) begin 22 | r <= {4{color_next&frame[1]}}; 23 | g <= {4{color_next&frame[0]}}; 24 | b <= {4{color_next&frame[2]}}; 25 | end 26 | 27 | // 28 | end 29 | // ==== Fractal ==== 30 | if (SELECT == 1) begin : fractal 31 | // 32 | 33 | wire valid_next = (10'd64 <= position_x_next) && (position_x_next < 10'd576); 34 | 35 | wire [8:0] shifted_x_next = valid_next ? 9'(position_x_next - 10'd64) : {9{1'bx}}; 36 | wire [8:0] shifted_y_next = valid_next ? 9'(position_y_next + 9'd16) : {9{1'bx}}; 37 | 38 | logic color_next; 39 | logic [3:0] r_next, g_next, b_next; 40 | integer i; 41 | always_comb begin 42 | color_next = 1'b1; 43 | for (i = 8; i >= 1; i=i-2) begin 44 | color_next = color_next & ((shifted_x_next[i]!=shifted_x_next[i-1]) || (shifted_y_next[i]!=shifted_y_next[i-1])); 45 | end 46 | r_next = valid_next?{4{color_next}}:4'b0; 47 | g_next = valid_next?{4{color_next}}:4'b0; 48 | b_next = valid_next?{4{color_next}}:4'b0; 49 | end 50 | 51 | always_ff @ (posedge clk) begin 52 | r <= r_next; 53 | g <= g_next; 54 | b <= b_next; 55 | end 56 | 57 | // 58 | end 59 | // ==== Bouncing Box ==== 60 | if (SELECT == 2) begin : box 61 | // 62 | 63 | `define SIGNED_CLAMP(MIN,T,MAX) (($signed(MIN) > $signed(T)) ? (MIN) : ($signed(MAX) < $signed(T)) ? (MAX) : (T)) 64 | 65 | localparam BOX_HEIGHT = 100; 66 | localparam BOX_WIDTH = 100; 67 | 68 | logic [$clog2(SCREEN_WIDTH):0] box_x, box_xv, box_x_next, box_xv_next, box_x_trajectory; 69 | logic [$clog2(SCREEN_HEIGHT):0] box_y, box_yv, box_y_next, box_yv_next, box_y_trajectory; 70 | 71 | /* verilator lint_off WIDTH */ 72 | 73 | wire hit_v_edge = ($signed(box_x_trajectory) < 0) || ($signed(box_x_trajectory) >= (SCREEN_WIDTH-BOX_WIDTH)); 74 | wire hit_h_edge = ($signed(box_y_trajectory) < 0) || ($signed(box_y_trajectory) >= (SCREEN_HEIGHT-BOX_HEIGHT)); 75 | 76 | assign box_x_trajectory = box_x + box_xv; 77 | assign box_y_trajectory = box_y + box_yv; 78 | assign box_x_next = `SIGNED_CLAMP(0, box_x_trajectory, (SCREEN_WIDTH-BOX_WIDTH)); 79 | assign box_y_next = `SIGNED_CLAMP(0, box_y_trajectory, (SCREEN_HEIGHT-BOX_HEIGHT)); 80 | 81 | // set velocity 82 | always_comb begin 83 | box_xv_next = box_xv; 84 | box_yv_next = box_yv; 85 | if (hit_v_edge) box_xv_next *= -1; 86 | if (hit_h_edge) box_yv_next *= -1; 87 | end 88 | 89 | wire in_box = 90 | ($signed(box_x) <= $unsigned(position_x) && $unsigned(position_x) < ($signed(box_x)+BOX_WIDTH)) 91 | && ($signed(box_y) <= $unsigned(position_y) && $unsigned(position_y) < ($signed(box_y)+BOX_HEIGHT)); 92 | 93 | /* verilator lint_on WIDTH */ 94 | 95 | 96 | wire [3:0] lightness = {{3{in_box}}, 1'b1}; 97 | logic [2:0] color, color_next; 98 | assign color_next = 99 | !(hit_v_edge||hit_h_edge) ? color : 100 | (color==3'b111) ? 3'b001 : 101 | (color + 1); 102 | 103 | assign r = lightness & {4{color[0]}}; 104 | assign g = lightness & {4{color[1]}}; 105 | assign b = lightness & {4{color[2]}}; 106 | 107 | logic [31:0] frame_prev; 108 | 109 | always_ff @ (posedge clk) begin 110 | if (rst) begin 111 | box_x <= 50; 112 | box_y <= 50; 113 | box_xv <= 2; 114 | box_yv <= 1; 115 | frame_prev <= 0; 116 | color <= 3'b111; 117 | end else if (frame_prev != frame) begin 118 | box_x <= box_x_next; 119 | box_y <= box_y_next; 120 | box_xv <= box_xv_next; 121 | box_yv <= box_yv_next; 122 | frame_prev <= frame; 123 | color <= color_next; 124 | end 125 | end 126 | 127 | `undef SIGNED_CLAMP 128 | 129 | // 130 | end 131 | // 132 | endgenerate 133 | 134 | endmodule 135 | -------------------------------------------------------------------------------- /rtl/top.sv: -------------------------------------------------------------------------------- 1 | 2 | module top #( 3 | parameter IMAGE_SELECT = 0 4 | ) ( 5 | input clk_25_175, rst, 6 | output logic hsync, vsync, 7 | output logic [3:0] r, g, b 8 | ); 9 | 10 | logic visible; 11 | logic [9:0] position_x, position_x_NEXT; 12 | logic [8:0] position_y, position_y_NEXT; 13 | logic [3:0] im_r, im_g, im_b; 14 | logic [31:0] frame; 15 | 16 | assign r = visible ? im_r : 0; 17 | assign g = visible ? im_g : 0; 18 | assign b = visible ? im_b : 0; 19 | 20 | video_timer #( 21 | .H_FRONT(16), 22 | .H_VISIBLE(640), 23 | .H_SYNC(96), 24 | .H_BACK(48), 25 | .V_FRONT(10), 26 | .V_VISIBLE(480), 27 | .V_SYNC(2), 28 | .V_BACK(33) 29 | ) vt ( 30 | clk_25_175, rst, 31 | hsync, vsync, 32 | visible, 33 | position_x, position_x_NEXT, 34 | position_y, position_y_NEXT, 35 | frame 36 | ); 37 | 38 | image #(IMAGE_SELECT) im ( 39 | clk_25_175, rst, 40 | position_x, position_x_NEXT, 41 | position_y, position_y_NEXT, 42 | frame, 43 | im_r, im_g, im_b 44 | ); 45 | 46 | 47 | // vga png generation 48 | `ifdef SIM 49 | 50 | integer f; 51 | initial f = $fopen("image.txt"); 52 | // at every clock pulse, print the color 53 | always @ (negedge clk_25_175) $fwrite(f, "%h%h0%h0%h0 ", (visible?8'hff:8'h00), b, g, r); 54 | // at the end of every hsync, print a line break 55 | always @ (posedge hsync) $fwrite(f, "\n"); 56 | 57 | `endif 58 | 59 | endmodule 60 | -------------------------------------------------------------------------------- /rtl/video_timer.sv: -------------------------------------------------------------------------------- 1 | 2 | // http://tinyvga.com/vga-timing 3 | module video_timer #( 4 | parameter H_VISIBLE = 640, 5 | parameter H_FRONT = 16, 6 | parameter H_SYNC = 96, 7 | parameter H_BACK = 48, 8 | parameter V_VISIBLE = 480, 9 | parameter V_FRONT = 10, 10 | parameter V_SYNC = 2, 11 | parameter V_BACK = 33 12 | ) ( 13 | input clk, rst, 14 | output logic hsync, vsync, 15 | output logic visible, 16 | output logic [$clog2(H_VISIBLE)-1:0] position_x, position_x_NEXT, 17 | output logic [$clog2(V_VISIBLE)-1:0] position_y, position_y_NEXT, 18 | output logic [31:0] frame 19 | ); 20 | 21 | 22 | // counter 23 | localparam WHOLE_LINE = (H_VISIBLE + H_FRONT + H_SYNC + H_BACK); 24 | localparam WHOLE_FRAME = (V_VISIBLE + V_FRONT + V_SYNC + V_BACK); 25 | logic [$clog2(WHOLE_LINE)-1:0] x_counter, x_counter_NEXT; 26 | logic [$clog2(WHOLE_FRAME)-1:0] y_counter, y_counter_NEXT; 27 | assign x_counter_NEXT = 28 | ( x_counter == (H_VISIBLE+H_FRONT+H_SYNC+H_BACK-1) ) ? 0 : 29 | x_counter + 1; 30 | assign y_counter_NEXT = 31 | ( x_counter != (H_VISIBLE+H_FRONT+H_SYNC+H_BACK-1) ) ? y_counter : 32 | ( y_counter == (V_VISIBLE+V_FRONT+V_SYNC+V_BACK-1) ) ? 0 : 33 | y_counter + 1; 34 | 35 | // whether in visible area 36 | logic hvisible, vvisible; 37 | assign hvisible = (x_counter < (H_VISIBLE)) && !rst; 38 | assign vvisible = (y_counter < (V_VISIBLE)) && !rst; 39 | assign visible = hvisible & vvisible; 40 | 41 | // horizontal and vertical sync 42 | assign hsync = ~( ((H_VISIBLE+H_FRONT) <= x_counter) && (x_counter < (H_VISIBLE+H_FRONT+H_SYNC)) && !rst ); 43 | assign vsync = ~( ((V_VISIBLE+V_FRONT) <= y_counter) && (y_counter < (V_VISIBLE+V_FRONT+V_SYNC)) && !rst ); 44 | 45 | // get current pixel coordinate 46 | `ifdef SIM 47 | assign position_x = visible ? $clog2(H_VISIBLE)'(x_counter) : {$clog2(H_VISIBLE){1'bx}}; 48 | assign position_y = visible ? $clog2(V_VISIBLE)'(y_counter) : {$clog2(V_VISIBLE){1'bx}}; 49 | `else 50 | assign position_x = $clog2(H_VISIBLE)'(x_counter); 51 | assign position_y = $clog2(V_VISIBLE)'(y_counter); 52 | `endif 53 | assign position_x_NEXT = $clog2(H_VISIBLE)'(x_counter_NEXT); 54 | assign position_y_NEXT = $clog2(V_VISIBLE)'(y_counter_NEXT); 55 | 56 | // unsigned integer counts how many frames have passed 57 | wire [31:0] frame_NEXT = 58 | ( y_counter != 0 && y_counter_NEXT == 0 ) ? frame+1 : 59 | frame; 60 | 61 | 62 | // flip flop 63 | always_ff @ ( posedge clk ) begin 64 | if ( rst ) begin 65 | x_counter <= (H_VISIBLE+H_FRONT+H_SYNC); // start at back porch 66 | y_counter <= (V_VISIBLE+V_FRONT+V_SYNC); // start at back porch 67 | frame <= ~0; // start at -1 68 | end else begin 69 | x_counter <= x_counter_NEXT; 70 | y_counter <= y_counter_NEXT; 71 | frame <= frame_NEXT; 72 | end 73 | 74 | // print frame number at every new frame 75 | `ifdef SIM 76 | if (frame != frame_NEXT) begin 77 | $timeformat( -3, 6, "ms", 0); 78 | $display( "Frame %0d begin: [Time=%0t]", frame_NEXT, $realtime ); 79 | end 80 | `endif 81 | end 82 | 83 | 84 | endmodule 85 | -------------------------------------------------------------------------------- /tangnano/Gowin_rPLL.v: -------------------------------------------------------------------------------- 1 | //Copyright (C)2014-2021 Gowin Semiconductor Corporation. 2 | //All rights reserved. 3 | //File Title: IP file 4 | //GOWIN Version: V1.9.8 5 | //Part Number: GW1N-LV1QN48C6/I5 6 | //Device: GW1N-1 7 | //Created Time: Sun Feb 20 17:00:51 2022 8 | 9 | module Gowin_rPLL (clkout, clkoutd, clkin); 10 | 11 | output clkout; 12 | output clkoutd; 13 | input clkin; 14 | 15 | wire lock_o; 16 | wire clkoutp_o; 17 | wire clkoutd3_o; 18 | wire gw_gnd; 19 | 20 | assign gw_gnd = 1'b0; 21 | 22 | rPLL rpll_inst ( 23 | .CLKOUT(clkout), 24 | .LOCK(lock_o), 25 | .CLKOUTP(clkoutp_o), 26 | .CLKOUTD(clkoutd), 27 | .CLKOUTD3(clkoutd3_o), 28 | .RESET(gw_gnd), 29 | .RESET_P(gw_gnd), 30 | .CLKIN(clkin), 31 | .CLKFB(gw_gnd), 32 | .FBDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 33 | .IDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 34 | .ODSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 35 | .PSDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 36 | .DUTYDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 37 | .FDLY({gw_gnd,gw_gnd,gw_gnd,gw_gnd}) 38 | ); 39 | 40 | defparam rpll_inst.FCLKIN = "24"; 41 | defparam rpll_inst.DYN_IDIV_SEL = "false"; 42 | defparam rpll_inst.IDIV_SEL = 4; 43 | defparam rpll_inst.DYN_FBDIV_SEL = "false"; 44 | defparam rpll_inst.FBDIV_SEL = 20; 45 | defparam rpll_inst.DYN_ODIV_SEL = "false"; 46 | defparam rpll_inst.ODIV_SEL = 8; 47 | defparam rpll_inst.PSDA_SEL = "0000"; 48 | defparam rpll_inst.DYN_DA_EN = "true"; 49 | defparam rpll_inst.DUTYDA_SEL = "1000"; 50 | defparam rpll_inst.CLKOUT_FT_DIR = 1'b1; 51 | defparam rpll_inst.CLKOUTP_FT_DIR = 1'b1; 52 | defparam rpll_inst.CLKOUT_DLY_STEP = 0; 53 | defparam rpll_inst.CLKOUTP_DLY_STEP = 0; 54 | defparam rpll_inst.CLKFB_SEL = "internal"; 55 | defparam rpll_inst.CLKOUT_BYPASS = "false"; 56 | defparam rpll_inst.CLKOUTP_BYPASS = "false"; 57 | defparam rpll_inst.CLKOUTD_BYPASS = "false"; 58 | defparam rpll_inst.DYN_SDIV_SEL = 4; 59 | defparam rpll_inst.CLKOUTD_SRC = "CLKOUT"; 60 | defparam rpll_inst.CLKOUTD3_SRC = "CLKOUT"; 61 | defparam rpll_inst.DEVICE = "GW1N-1"; 62 | 63 | endmodule //Gowin_rPLL 64 | -------------------------------------------------------------------------------- /tangnano/pins.cst: -------------------------------------------------------------------------------- 1 | 2 | IO_LOC "clk_24" 35; 3 | 4 | IO_LOC "hsync" 10; 5 | IO_LOC "vsync" 11; 6 | 7 | IO_LOC "r[0]" 28; 8 | IO_LOC "r[1]" 29; 9 | IO_LOC "r[2]" 30; 10 | IO_LOC "r[3]" 31; 11 | IO_LOC "g[0]" 34; 12 | IO_LOC "g[1]" 38; 13 | IO_LOC "g[2]" 39; 14 | IO_LOC "g[3]" 40; 15 | IO_LOC "b[0]" 42; 16 | IO_LOC "b[1]" 43; 17 | IO_LOC "b[2]" 44; 18 | IO_LOC "b[3]" 45; 19 | 20 | IO_LOC "An" 15; 21 | IO_LOC "Bn" 14; 22 | -------------------------------------------------------------------------------- /tangnano/tangnano.sv: -------------------------------------------------------------------------------- 1 | 2 | module tangnano #( 3 | parameter IMAGE_SELECT = 0 4 | ) ( 5 | input clk_24, 6 | input An, Bn, 7 | output logic hsync, vsync, 8 | output logic [3:0] r, g, b 9 | ); 10 | 11 | 12 | wire rst = ~An; 13 | logic clk_25_175; 14 | 15 | // PLL to convert 24MHz to 25.175MHz 16 | Gowin_rPLL pll ( .clkoutd(clk_25_175), .clkin(clk_24) ); 17 | top #(IMAGE_SELECT) t (.*); 18 | 19 | endmodule 20 | -------------------------------------------------------------------------------- /tb/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | Pillow 3 | -------------------------------------------------------------------------------- /tb/to_png.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os import path 3 | from PIL import Image 4 | import numpy as np 5 | 6 | # MAIN 7 | if __name__ == '__main__': 8 | 9 | filein = "" 10 | fileout = "" 11 | 12 | try: 13 | filein_name = sys.argv[1] 14 | fileout_name = sys.argv[2] 15 | except IndexError: 16 | print("[Argument error]: filein and fileout not given") 17 | exit(1) 18 | 19 | # Load File 20 | data = [] 21 | width = 0 22 | with open(filein_name, 'r') as filein: 23 | for line in filein.readlines(): 24 | list_of_strings = line.split(' ')[:-1] 25 | list_of_colors = [ int(c.replace('x','0'), 16) for c in list_of_strings] 26 | width = max(width, len(list_of_colors)) 27 | data.append( list_of_colors ) 28 | for line in data: 29 | line += [0] * (width - len(line)) 30 | height = len(data) 31 | 32 | # Convert to PNG 33 | np_data = np.array(data, np.uint32).reshape(height, width) 34 | # np.set_printoptions(threshold=np.inf, linewidth=np.inf) 35 | # print(np_data) 36 | im = Image.fromarray(np_data, 'RGBA') 37 | im.save(fileout_name) 38 | 39 | print("Generated image at " + path.abspath(fileout_name)) 40 | -------------------------------------------------------------------------------- /tb/top.tb.sv: -------------------------------------------------------------------------------- 1 | 2 | `timescale 1s/1ns 3 | 4 | module top_tb #( 5 | parameter IMAGE_SELECT = 0, 6 | parameter FREQ = 25175000.0 7 | ) (); 8 | 9 | logic clk = 1; 10 | always #(1.0/FREQ) clk <= ~clk; 11 | 12 | logic rst = 0; 13 | 14 | logic hsync; 15 | logic vsync; 16 | logic visible; 17 | logic [3:0] r, g, b; 18 | 19 | top #(IMAGE_SELECT) t ( 20 | .clk_25_175(clk), 21 | .rst(rst), 22 | .hsync(hsync), 23 | .vsync(vsync), 24 | .r(r), .g(g), .b(b) 25 | ); 26 | 27 | initial begin : sim 28 | $dumpfile( "dump.fst" ); 29 | $dumpvars; 30 | $display( "Begin simulation."); 31 | //\\ =========================== \\// 32 | 33 | rst = 1; 34 | @(posedge clk); 35 | @(posedge clk); 36 | rst = 0; 37 | 38 | // ==== Checkerboard ==== 39 | if (IMAGE_SELECT == 0) begin 40 | // 41 | 42 | for (integer i = 0; i < 8; i=i+1) 43 | @(negedge vsync); 44 | 45 | // 46 | end 47 | // ==== Fractal ==== 48 | if (IMAGE_SELECT == 1) begin 49 | // 50 | 51 | @(negedge vsync); 52 | 53 | // 54 | end 55 | // ==== Bouncing Box ==== 56 | if (IMAGE_SELECT == 2) begin 57 | // 58 | 59 | for (integer i = 0; i < 8; i=i+1) 60 | @(negedge vsync); 61 | 62 | // 63 | end 64 | 65 | //\\ =========================== \\// 66 | $display( "End simulation."); 67 | $finish; 68 | end 69 | 70 | endmodule 71 | -------------------------------------------------------------------------------- /top.core: -------------------------------------------------------------------------------- 1 | CAPI=2: 2 | # 3 | # Recommended reading about FuseSoC core files: https://fusesoc.readthedocs.io/en/stable/user/build_system/core_files.html 4 | # 5 | name: ucsbieee::fpga_screensaver:1.0.0 6 | description: screensaver 7 | 8 | # This section describes which functional groups each of the source files belong 9 | # to and in what language they're written. 10 | filesets: 11 | # default 12 | rtl: 13 | files: 14 | - rtl/top.sv 15 | - rtl/image.sv 16 | - rtl/video_timer.sv 17 | file_type: systemVerilogSource 18 | 19 | tb: 20 | files: 21 | - tb/top.tb.sv: {file_type: systemVerilogSource} 22 | - tb/to_png.py: {file_type: user, copyto: to_png.py} 23 | 24 | lint: 25 | files: 26 | - lint.vlt: {file_type: vlt} 27 | 28 | usage: 29 | files: 30 | - usage/post.tcl: {file_type: user, copyto: post.tcl} 31 | 32 | tangnano: 33 | files: 34 | - tangnano/Gowin_rPLL.v: {file_type: verilogSource} 35 | - tangnano/tangnano.sv: {file_type: systemVerilogSource} 36 | - tangnano/pins.cst: {file_type: CST} 37 | 38 | # This section defines the different FuseSoC build targets that can be called 39 | # to either simulate (in some way) or synthesize the FPGA design. Each build 40 | # target specifies which files it requires and how to call the tool specific to 41 | # that process. 42 | targets: 43 | default: &default 44 | filesets: 45 | - rtl 46 | parameters: 47 | - IMAGE_SELECT=2 48 | 49 | tb: # fusesoc run --target tb ucsbieee::fpga_screensaver 50 | <<: *default 51 | description: Simulate the design 52 | filesets_append: 53 | - tb 54 | toplevel: top_tb 55 | default_tool: icarus 56 | tools: 57 | icarus: 58 | iverilog_options: 59 | - -g2012 60 | - -Wall 61 | - -Wno-timescale 62 | - -DSIM 63 | 64 | lint: # fusesoc run --target lint ucsbieee::fpga_screensaver 65 | <<: *default 66 | description: Simulate the design 67 | filesets_append: 68 | - lint 69 | toplevel: top 70 | default_tool: verilator 71 | tools: 72 | verilator: 73 | mode: lint-only 74 | verilator_options: ["-Wall"] 75 | 76 | usage: # fusesoc run --target usage ucsbieee::fpga_screensaver 77 | <<: *default 78 | description: Synthesize 79 | filesets_append: 80 | - usage 81 | toplevel: top 82 | default_tool: yosys 83 | tools: 84 | yosys: 85 | arch: ice40 86 | output_format: json 87 | hooks: 88 | pre_build: 89 | - convert_to_prep 90 | post_build: 91 | - record_usage 92 | 93 | tangnano: # fusesoc run --target tangnano ucsbieee::fpga_screensaver 94 | <<: *default 95 | description: Synthesize for Tang Nano 96 | default_tool: apicula 97 | filesets_append: 98 | - tangnano 99 | tools: 100 | apicula: 101 | device: GW1N-LV1QN48C6/I5 102 | toplevel: tangnano 103 | 104 | # This section defines non-Verilog scripts (e.g. shell commands or Python scripts) 105 | # which are used in the above build targets to simulate the PPGA design. 106 | scripts: 107 | convert_to_prep: 108 | cmd: [sed, -i, 's/synth_ice40/prep/g', edalize_yosys_procs.tcl] 109 | record_usage: 110 | cmd: [yosys, -c, post.tcl] 111 | filesets: [usage] 112 | 113 | # This section defines human-readable descriptions of the command-line parameters 114 | # used in the above guild targets. 115 | parameters: 116 | IMAGE_SELECT: 117 | datatype: int 118 | description: Select which image to use 119 | paramtype: vlogparam 120 | -------------------------------------------------------------------------------- /usage/post.tcl: -------------------------------------------------------------------------------- 1 | yosys -import 2 | source edalize_yosys_procs.tcl 3 | 4 | read_json $name.json 5 | tee -o usage.txt stat 6 | --------------------------------------------------------------------------------