commit:7428bdc8ceb4d3c4ed4c7be23137f9e19d85cb27
author:Chip
committer:Chip
date:Mon Sep 2 00:14:13 2024 -0500
parents:95a8fb0345eff95dcbef8349dec7c249da5af287
Update docs; add clockgen
diff --git a/README.md b/README.md
line changes: +58/-24
index 68ba6ed..86f6933
--- a/README.md
+++ b/README.md
@@ -1,8 +1,16 @@
-![](../../workflows/gds/badge.svg) ![](../../workflows/docs/badge.svg) ![](../../workflows/test/badge.svg) ![](../../workflows/fpga/badge.svg)
+![](../../workflows/gds/badge.svg) ![](../../workflows/docs/badge.svg) ![](../../workflows/test/badge.svg)
 
-# Tiny Tapeout Verilog Project Template
+# Munch
 
-- [Read the documentation for project](docs/info.md)
+Munch is an audiovisual presentation (a
+["demo"](https://en.wikipedia.org/wiki/Demoscene)) for the [Tiny Tapeout
+Demoscene competition](https://tinytapeout.com/competitions/demoscene/).
+It shows a munching squares animation and some text via [Leo's VGA
+PMOD](https://github.com/mole99/tiny-vga), and plays some music via the
+[Tiny Tapeout Audio Pmod](https://github.com/MichaelBell/tt-audio-pmod).
+
+For more technical details, [read the documentation for
+project](docs/info.md).
 
 ## What is Tiny Tapeout?
 
@@ -10,32 +18,58 @@ Tiny Tapeout is an educational project that aims to make it easier and cheaper t
 
 To learn more and get started, visit https://tinytapeout.com.
 
-## Set up your Verilog project
+## Running on the iCEstick
+
+In addition to the standard Tiny Tapeout OpenLane build, this can also
+be built for the Lattice iCEstick. Run `make icestick` to produce
+`munch.bin`, which can be programmed onto your iCEstick with `iceprog
+munch.bin`.
+
+You will of course need the usual tools - [yosys](https://github.com/YosysHQ/yosys),
+[nextpnr](https://github.com/YosysHQ/nextpnr), and [Project
+IceStorm](https://github.com/YosysHQ/icestorm). You can get everything
+in one convenient package from the [Yosys OSS CAD
+Suite](https://github.com/YosysHQ/oss-cad-suite-build).
+
+This _should_ produce a bitstream that is functionally identical to the
+ASIC version, and, with the interface noted below, it will follow all
+the descriptions from the [main TT documentation](docs/info.md).
+
+### Pin mapping
+
+The Tiny Tapeout interface (`ui_in`, `uo_out`, etc.) are mapped to
+pins on the iCEstick with `src/iceshim.v`.
 
-1. Add your Verilog files to the `src` folder.
-2. Edit the [info.yaml](info.yaml) and update information about your project, paying special attention to the `source_files` and `top_module` properties. If you are upgrading an existing Tiny Tapeout project, check out our [online info.yaml migration tool](https://tinytapeout.github.io/tt-yaml-upgrade-tool/).
-3. Edit [docs/info.md](docs/info.md) and add a description of your project.
-4. Adapt the testbench to your design. See [test/README.md](test/README.md) for more information.
+The main PMOD connector (port1, pins 78-91) is configured as `uo_out`.
+The two other headers are mapped to `ui_in` (port0, 112-119) and
+`uio_out` (port2, 44-62) except for the two lowest pins on port2.
+`port2[0]` is the external clock input `clk` and `port2[1]` is the reset
+line `rst_n`.
 
-The GitHub action will automatically build the ASIC files using [OpenLane](https://www.zerotoasiccourse.com/terminology/openlane/).
+Additionally, `uio_out[6:4]` is mapped to the first three LEDs. It
+displays the internal state of the top three bits of the pattern clock.
 
-## Enable GitHub actions to build the results page
+### External clock
 
-- [Enabling GitHub Pages](https://tinytapeout.com/faq/#my-github-action-is-failing-on-the-pages-part)
+The iCEstick's internal clock is fixed at 12MHz (or at least, if it is
+adjustable I didn't bother figuring out how). I used a Pi Pico to
+generate a clock to better emulate the Tiny Tapeout dev board hardware.
+I have a program in `clockgen` that can do this because I was not sure
+if the TT micropython firmware would work on a regular Pico. It outputs
+a 25.177MHz clock on GPIO 21 (pin 27). It can also be tuned by
+connecting to it via serial and pressing + or - to adjust the clock
+divider.
 
-## Resources
+### Running
 
-- [FAQ](https://tinytapeout.com/faq/)
-- [Digital design lessons](https://tinytapeout.com/digital_design/)
-- [Learn how semiconductors work](https://tinytapeout.com/siliwiz/)
-- [Join the community](https://tinytapeout.com/discord)
-- [Build your design locally](https://www.tinytapeout.com/guides/local-hardening/)
+The demo will start directly after programming/power on, but it needs
+the reset line brought low to properly initialize all the registers. It
+should be brought low for at least three periods of the input clock.
 
-## What next?
+## Supplemental tests
 
-- [Submit your design to the next shuttle](https://app.tinytapeout.com/).
-- Edit [this README](README.md) and explain your design, how it works, and how to test it.
-- Share your project on your social network of choice:
-  - LinkedIn [#tinytapeout](https://www.linkedin.com/search/results/content/?keywords=%23tinytapeout) [@TinyTapeout](https://www.linkedin.com/company/100708654/)
-  - Mastodon [#tinytapeout](https://chaos.social/tags/tinytapeout) [@matthewvenn](https://chaos.social/@matthewvenn)
-  - X (formerly Twitter) [#tinytapeout](https://twitter.com/hashtag/tinytapeout) [@tinytapeout](https://twitter.com/tinytapeout)
+There are two module tests under `test/` for testing the LFSR and audio
+output. The LFSR test `make -f Makefile.lfsr` just ensures that the LFSR
+period is the full 11 bits. The audio test (`make -f Makefile.audio`)
+doesn't actually test anything. I just use it to inspect the waveforms
+with GTKWave.

diff --git a/clockgen/CMakeLists.txt b/clockgen/CMakeLists.txt
line changes: +18/-0
index 0000000..a8a3360
--- /dev/null
+++ b/clockgen/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.22)
+
+include(pico_sdk_import.cmake)
+
+project(clockgen C CXX ASM)
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+
+pico_sdk_init()
+
+add_compile_options(-Wall -Wextra)
+add_executable(clockgen clockgen.c)
+
+target_link_libraries(clockgen pico_stdlib pico_stdio pico_printf hardware_clocks)
+
+pico_enable_stdio_usb(clockgen 1)
+
+pico_add_extra_outputs(clockgen)

diff --git a/clockgen/clockgen.c b/clockgen/clockgen.c
line changes: +70/-0
index 0000000..114b307
--- /dev/null
+++ b/clockgen/clockgen.c
@@ -0,0 +1,70 @@
+#include <stdio.h>
+#include "pico/stdlib.h"
+#include "hardware/clocks.h"
+#include "hardware/structs/clocks.h"
+
+#define PWM_PIN 16
+#define PWM_SLICE 0
+
+void print_rate(uint32_t div, uint8_t frac) {
+	printf("PWM set to %f MHz (%ld:%d).\n", 125.0 / (div + (frac / 256.0)), div, frac);
+}
+
+void print_prompt() {
+	printf("\n> ");
+}
+
+void rate_add(uint32_t* div, uint8_t* frac, int8_t delta) {
+	uint32_t tmp = (*div << 8) | *frac;
+	tmp += delta;
+	*div = (tmp >> 8) & 0xFFFFFF;
+	*frac = tmp & 0xFF;
+}
+
+void rate_update(uint32_t div, uint8_t frac) {
+	clock_gpio_init_int_frac(
+		21,
+		CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
+		div,
+		frac
+	);
+}
+
+int main() {
+	uint32_t div = 4;
+	uint8_t frac = 247;
+
+	stdio_init_all();
+
+	gpio_init(PICO_DEFAULT_LED_PIN);
+	gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
+
+	sleep_ms(2000);
+
+	print_rate(div, frac);
+	print_prompt();
+
+	rate_update(div, frac);
+
+	while (true) {
+		char c = getchar();
+		gpio_put(PICO_DEFAULT_LED_PIN, 1);
+		switch (c) {
+		case '\r':
+			print_rate(div, frac);
+			break;
+		case '-':
+			rate_add(&div, &frac, 1);
+			rate_update(div, frac);
+			print_rate(div, frac);
+			break;
+		case '+':
+			rate_add(&div, &frac, -1);
+			rate_update(div, frac);
+			print_rate(div, frac);
+			break;
+		}
+		gpio_put(PICO_DEFAULT_LED_PIN, 0);
+		print_prompt();
+	}
+}

diff --git a/clockgen/pico_sdk_import.cmake b/clockgen/pico_sdk_import.cmake
line changes: +84/-0
index 0000000..a0721d0
--- /dev/null
+++ b/clockgen/pico_sdk_import.cmake
@@ -0,0 +1,84 @@
+# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+    set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+    message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+    set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+    message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+    set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+    message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+    set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+    message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+  set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+  message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+    if (PICO_SDK_FETCH_FROM_GIT)
+        include(FetchContent)
+        set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+        if (PICO_SDK_FETCH_FROM_GIT_PATH)
+            get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+        endif ()
+        # GIT_SUBMODULES_RECURSE was added in 3.17
+        if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+            FetchContent_Declare(
+                    pico_sdk
+                    GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+                    GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+                    GIT_SUBMODULES_RECURSE FALSE
+            )
+        else ()
+            FetchContent_Declare(
+                    pico_sdk
+                    GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+                    GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+            )
+        endif ()
+
+        if (NOT pico_sdk)
+            message("Downloading Raspberry Pi Pico SDK")
+            FetchContent_Populate(pico_sdk)
+            set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+        endif ()
+        set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+    else ()
+        message(FATAL_ERROR
+                "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+                )
+    endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+    message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+    message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})

diff --git a/docs/info.md b/docs/info.md
line changes: +63/-17
index 8b29df0..8c22271
--- a/docs/info.md
+++ b/docs/info.md
@@ -13,48 +13,94 @@ This generates VGA output for a munching squares animation plus some
 other stuff, and some simple music. It uses the VGA and audio PMODs
 listed below.
 
+<img src="munch.png">
+
+### Clock generation
+
+A clock generator module divides the main clock `clk` and `vsync` into
+two derived clocks - a 393kHz PWM clock for audio output, and a 10Hz
+"audio tick" clock that drives the pattern sequencer. The clock
+generator also provides counters for PWM, volume modulation, and audio
+pattern sequencing.
+
 ### LFSR
 
-To provide noise, there is an 11-bit LFSR. It uses the XNOR type,
-shifting right, with taps on bits 0 and 2. Bits 0-5 are tapped to
-provide a noise channel and randomized video noise dithering.
+An 11-bit LFSR provides a noise source. It is an XNOR type, shifting
+down towards the LSB and inserting the new bit at MSB. XNOR taps are on
+bits 0 and 2. Bits 0-5 of the LFSR register are used to provide a noise
+channel and randomized video noise dithering.
 
 ### Video
 
 The video output is the standard 640x480 @ 60Hz, using a 25.175MHz pixel
-clock.
+clock and negative polarity HSYNC/VSYNC. Timing is implemented with a
+simple two-counter design shamelessly stolen from the Tiny Tapeout VGA
+playground.
+
+A fixed palette of eight colors is used, and eight brightness levels are
+created by mixing random bits with the 2-bit per channel brightness
+levels. Video is output on three layers - the background and layers 0
+and 1. A non-black pixel overrides a pixel on any lower layer.
 
 ### Audio
 
-Audio comes from a basic PSG. There are four channels of sound based on
-12-bit timers - three pulse channels and one noise channel. The only
-real difference between a pulse channel and a noise channel is that the
-pulse channel flips state when the timer counts down, and the noise
-channel takes a random state from the LFSR. Each channel also has a
-two-bit volume level.
+Audio comes from a basic PSG inspired by the SN76489. There are four
+channels of sound based on 12-bit timers - three pulse channels and one
+noise channel. The only real difference between a pulse channel and a
+noise channel is that the pulse channel flips state when the timer
+counts down, and the noise channel takes a random state from the LFSR.
+Each channel also has a two-bit volume level.
 
 The 25.175MHz clock is divided by a 6-bit counter to create a 393KHz PWM
-output. 6-bits gives 64 possible levels. The PWM output is a simple sum
-of the four channels' volumes at any given instant.
+output. 6-bits gives 64 possible levels. The PWM high period is a simple
+sum of the four channels' volumes at any given instant (multiplied by
+two with the low bit dithered from the LFSR). This does mean the PWM
+will glitch if volume levels change in the middle of a PWM cycle, but
+that's fine in practice since it's all low-pass filtered anyway.
 
-The four channels are sequenced through a sequencer that provides note
+The four channels are programmed through a sequencer that provides note
 and volume data to the PSG. The sequencer is clocked by dividing VSYNC
 by 6, so the sequencer moves through pattern rows at 10Hz, or 600 ticks
 per minute. Each pattern of 16 ticks represents one measure, four beats,
 which means the music proceeds at 150 BPM.
 
-The sequencer cycles through pre-programmed patterns of notes. Volumes
-are modulated through a single repeating pattern per channel, indexed
-from the sequencer clock divider.
+The sequencer cycles through pre-programmed patterns of notes. Note
+timer data is read from an indirected list of notes, then connected
+directly to the PSG reload values. This does mean the oscillators are
+not synchronized at note start. Volumes are modulated through a single
+repeating pattern per channel, indexed from the top two bits of the
+sequencer div-by-6 clock divider. This means the volume is a three-step
+pattern cycling at the start of each pattern tick.
 
 ### Text Generator
 
 On-screen text uses a segmented approach, where each segment is defined
-by a mathematical description of a line segment. Each character is
+by a mathematical description of a line segment. Each character is then
 defined by which segments are off or on, like a multi-segment LED
 display. So text is generated at full resolution despite its large size;
 each character is 50x100 pixels.
 
+The text generator is just a sequencer over an input bit stream, indexed
+by the horizonal and vertical position. In this implementation the input
+is at most six characters long. The text can be positioned arbitrarily,
+but for this demo it is fixed.
+
+### Stage Sequencer
+
+A slower stage clock is derived from the pattern clock. It ticks once
+every pattern cycle, and drives an overarching "stage sequencer". Each
+stage counts down for a pre-programmed number of patterns, then switches
+to the next. The stage number is used in various logic to change the
+text and colors over time.
+
+### Extra outputs
+
+In addition to the audio and video, the three highest bits of the
+internal pattern counter are output on `uio_out[6:4]`. The two highest
+bits count out the four beats in a pattern, and bit 1 has a negative
+edge at the beginning of each beat. This could be used for beat
+synchronization with external systems - I just used it for debugging.
+
 ## How to test
 
 Set the input clock for 25.175MHz. The Pico/RP2040 can output 25.177MHz on

diff --git a/docs/munch.png b/docs/munch.png
line changes: +0/-0
index 0000000..fad4027
--- /dev/null
+++ b/docs/munch.png

diff --git a/info.yaml b/info.yaml
line changes: +3/-3
index 2f64d62..4034ed1
--- a/info.yaml
+++ b/info.yaml
@@ -49,9 +49,9 @@ pinout:
   uio[1]: ""
   uio[2]: ""
   uio[3]: ""
-  uio[4]: ""
-  uio[5]: ""
-  uio[6]: ""
+  uio[4]: "beat clock bit 1 (output)"
+  uio[5]: "beat clock bit 2 (output)"
+  uio[6]: "beat clock bit 3 (output)"
   uio[7]: "audio (output)"
 
 # Do not change!