commit:7eee669bdb5f6b03bb5214788190a06517c7df84
author:Chip
committer:Chip
date:Wed Aug 28 19:56:44 2024 -0500
parents:0f8cafbda47797b07a1f3d2a32c1e902d420503b
Mostly working
diff --git a/Makefile b/Makefile
line changes: +22/-0
index 0000000..5b310ef
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+SOURCES = hvsync_generator.v audio.v video.v project.v
+
+PSOURCES = $(addprefix src/,$(SOURCES))
+
+.PHONY: check
+check:
+	yosys -p 'read -vlog2k $(PSOURCES); check'
+
+.PHONY: lint
+lint:
+	verilator --lint-only -Wall -Wno-DECLFILENAME $(PSOURCES)
+
+munch.vvp: $(PSOURCES)
+	iverilog -v -o $@ $(PSOURCES)
+
+.PHONY: icestick
+icestick: munch.bin
+
+munch.bin: $(PSOURCES) src/iceshim.v
+	yosys icestick.ys
+	nextpnr-ice40 --hx1k --package tq144 --json munch.json --pcf icestick.pcf --asc munch.asc --freq 25.175
+	icepack munch.asc $@

diff --git a/icestick.pcf b/icestick.pcf
line changes: +34/-0
index 0000000..10c802b
--- /dev/null
+++ b/icestick.pcf
@@ -0,0 +1,34 @@
+set_io port0[0] 112
+set_io port0[1] 113
+set_io port0[2] 114
+set_io port0[3] 115
+set_io port0[4] 116
+set_io port0[5] 117
+set_io port0[6] 118
+set_io port0[7] 119
+
+set_io port1[0] 78
+set_io port1[1] 79
+set_io port1[2] 80
+set_io port1[3] 81
+set_io port1[4] 87
+set_io port1[5] 88
+set_io port1[6] 90
+set_io port1[7] 91
+
+set_io port2[0] 44
+set_io port2[1] 45
+set_io port2[2] 47
+set_io port2[3] 48
+set_io port2[4] 56
+set_io port2[5] 60
+set_io port2[6] 61
+set_io port2[7] 62
+
+set_io leds[0] 99
+set_io leds[1] 98
+set_io leds[2] 97
+set_io leds[3] 96
+set_io leds[4] 95
+
+set_io clk 21

diff --git a/icestick.ys b/icestick.ys
line changes: +3/-0
index 0000000..3e9f953
--- /dev/null
+++ b/icestick.ys
@@ -0,0 +1,3 @@
+read_verilog src/hvsync_generator.v src/audio.v src/video.v src/project.v src/iceshim.v
+synth_ice40 -top iceshim
+write_json munch.json

diff --git a/info.yaml b/info.yaml
line changes: +2/-0
index c7b3e5b..3ec2971
--- a/info.yaml
+++ b/info.yaml
@@ -18,6 +18,8 @@ project:
   # Don't forget to also update `PROJECT_SOURCES` in test/Makefile.
   source_files:
     - "hvsync_generator.v"
+    - "audio.v"
+    - "video.v"
     - "project.v"
 
 # The pinout of your project. Leave unused pins blank. DO NOT delete or add any pins.

diff --git a/src/Makefile b/src/Makefile
line changes: +0/-4
index a1d100b..0000000
--- a/src/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-SOURCES = hvsync_generator.v project.v
-
-munch.vvp: $(SOURCES)
-	iverilog -o $@ $(SOURCES)

diff --git a/src/audio.v b/src/audio.v
line changes: +593/-0
index 0000000..6307d32
--- /dev/null
+++ b/src/audio.v
@@ -0,0 +1,593 @@
+module audio(
+  input wire [5:0] pwm_clock,
+  input wire [1:0] atick_clock,
+  input wire [3:0] pattern_clock,
+  input wire rst_n,
+  input wire rng,
+  output wire audio
+);
+  wire [11:0] audio_time0;  // per-channel oscillator timer values
+  wire [11:0] audio_time1;
+  wire [11:0] audio_time2;
+  wire [11:0] audio_time3;
+  wire [1:0] audio_vol0;    // per-channel volume values
+  wire [1:0] audio_vol1;
+  wire [1:0] audio_vol2;
+  wire [1:0] audio_vol3;
+
+  sequencer seq(
+    .tick_clock(atick_clock[1:0]),
+    .pattern_clock(pattern_clock),
+    .rst_n(rst_n),
+    .freq0(audio_time0),
+    .freq1(audio_time1),
+    .freq2(audio_time2),
+    .freq3(audio_time3),
+    .vol0(audio_vol0),
+    .vol1(audio_vol1),
+    .vol2(audio_vol2),
+    .vol3(audio_vol3)
+  );
+
+  audio_psg audio_gen(
+    .pwm_clock(pwm_clock),
+    .rst_n(rst_n),
+    .timer0(audio_time0),
+    .timer1(audio_time1),
+    .timer2(audio_time2),
+    .timer3(audio_time3),
+    .vol0(audio_vol0),
+    .vol1(audio_vol1),
+    .vol2(audio_vol2),
+    .vol3(audio_vol3),
+    .rng(rng),
+    .audio(audio)
+  );
+
+endmodule
+
+/* PSG with three square wave channels and one noise channel
+ * 2-bit volume per channel
+ */
+module audio_psg(
+  input wire [5:0] pwm_clock,
+  input wire rst_n,
+  input wire [11:0] timer0,
+  input wire [11:0] timer1,
+  input wire [11:0] timer2,
+  input wire [11:0] timer3,
+  input wire [1:0] vol0,
+  input wire [1:0] vol1,
+  input wire [1:0] vol2,
+  input wire [1:0] vol3,
+  input wire rng,
+  output wire audio
+);
+
+  wire [5:0] level;
+  wire f_clock;
+  wire ch0_state, ch1_state, ch2_state, ch3_state;
+
+  assign f_clock = (pwm_clock == 0);
+
+  audio_psg_square_gen chan0(
+    .clk(f_clock),
+    .rst_n(rst_n),
+    .timer(timer0),
+    .out(ch0_state)
+  );
+  audio_psg_square_gen chan1(
+    .clk(f_clock),
+    .rst_n(rst_n),
+    .timer(timer1),
+    .out(ch1_state)
+  );
+  audio_psg_square_gen chan2(
+    .clk(f_clock),
+    .rst_n(rst_n),
+    .timer(timer2),
+    .out(ch2_state)
+  );
+  audio_psg_noise_gen chan3(
+    .clk(f_clock),
+    .rst_n(rst_n),
+    .timer(timer3),
+    .rng(rng),
+    .out(ch3_state)
+  );
+
+  // There's probably a better way to do this.
+  assign level = 31 + {(ch0_state ? {3'b0, vol0} : -{3'b0, vol0}), rng}
+                    + {(ch1_state ? {3'b0, vol1} : -{3'b0, vol1}), rng}
+                    + {(ch2_state ? {3'b0, vol2} : -{3'b0, vol2}), rng}
+                    + {(ch3_state ? {3'b0, vol3} : -{3'b0, vol3}), rng};
+  assign audio = pwm_clock <= level;
+endmodule
+
+/* Signal generator for noise channel */
+module audio_psg_noise_gen(
+  input wire clk,           // the audio clock, which is the main clock
+                            // divided by 64.
+  input wire rst_n,
+  input wire [11:0] timer,  // the timer value
+  input wire rng,           // random bit from lfsr
+  output wire out           // the square wave output
+);
+
+  reg [11:0] ch_counter;
+  reg ch_state;
+
+  always @(posedge clk, negedge rst_n) begin
+    if (!rst_n) begin
+      ch_counter <= 0;
+      ch_state <= 0;
+    end
+    else begin
+      if (ch_counter == timer) begin
+        ch_state <= rng;
+        ch_counter <= 0;
+      end
+      else
+        ch_counter <= ch_counter + 1;
+    end
+  end
+
+  assign out = ch_state;
+
+endmodule
+
+/* Signal generator for square wave channel */
+module audio_psg_square_gen(
+  input wire clk,           // the audio clock, which is the main clock
+                            // divided by 64.
+  input wire rst_n,
+  input wire [11:0] timer,  // the timer value
+  output wire out           // the square wave output
+);
+
+  reg [11:0] ch_counter;
+  reg ch_state;
+
+  always @(posedge clk, negedge rst_n) begin
+    if (!rst_n) begin
+      ch_counter <= 0;
+      ch_state <= 0;
+    end
+    else begin
+      if (ch_counter == timer) begin
+        ch_state <= !ch_state;
+        ch_counter <= 0;
+      end
+      else
+        ch_counter <= ch_counter + 1;
+    end
+  end
+
+  assign out = ch_state;
+
+endmodule
+
+parameter T_Fs1 = 12'd2126; // 185    Hz
+parameter T_Gs1 = 12'd1894; // 207.65 Hz
+parameter T_A1  = 12'd1788; // 220    Hz
+parameter T_As1 = 12'd1688; // 233.08 Hz
+parameter T_B1  = 12'd1593; // 246.94 Hz
+parameter T_C2  = 12'd1503; // 261.63 Hz
+parameter T_Fs2 = 12'd1063; // 370    Hz
+parameter T_Gs2 = 12'd947;  // 415.3  Hz
+parameter T_As2 = 12'd844;  // 466.16 Hz
+parameter T_B2  = 12'd796;  // 493.88 Hz
+parameter T_C3  = 12'd752;  // 523.25 Hz
+parameter T_C4  = 12'd376;  // 1046.5 Hz
+parameter T_B7  = 12'd100;  // 3951   Hz
+
+parameter N_Fs1 = 1;
+parameter N_Gs1 = 2;
+parameter N_A1  = 3;
+parameter N_As1 = 4;
+parameter N_B1  = 5;
+parameter N_C2  = 6;
+parameter N_Fs2 = 7;
+parameter N_Gs2 = 8;
+parameter N_As2 = 9;
+parameter N_B2  = 10;
+parameter N_C3  = 11;
+parameter N_C4  = 12;
+parameter N_B7  = 13;
+
+module note_map(
+  input wire [3:0] select,
+  output wire [11:0] freq
+);
+  wire [11:0] notes [13:0];
+
+  assign notes[0]     = 0;
+  assign notes[N_Fs1] = T_Fs1;
+  assign notes[N_Gs1] = T_Gs1;
+  assign notes[N_A1]  = T_A1;
+  assign notes[N_As1] = T_As1;
+  assign notes[N_B1]  = T_B1;
+  assign notes[N_C2]  = T_C2;
+  assign notes[N_Fs2] = T_Fs2;
+  assign notes[N_Gs2] = T_Gs2;
+  assign notes[N_As2] = T_As2;
+  assign notes[N_B2]  = T_B2;
+  assign notes[N_C3]  = T_C3;
+  assign notes[N_C4]  = T_C4;
+  assign notes[N_B7]  = T_B7;
+
+  assign freq = notes[select];
+endmodule
+
+// Turn this off for the pattern section, since they all have the same
+// interface but they're not all using the select lines.
+// verilator lint_off UNUSEDSIGNAL
+
+module pattern1_1(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  wire [3:0] note_sequence [15:0];
+
+  assign note_sequence[0]  = N_C2;
+  assign note_sequence[1]  = 0;
+  assign note_sequence[2]  = 0;
+  assign note_sequence[3]  = N_C2;
+  assign note_sequence[4]  = 0;
+  assign note_sequence[5]  = 0;
+  assign note_sequence[6]  = N_C2;
+  assign note_sequence[7]  = 0;
+  assign note_sequence[8]  = 0;
+  assign note_sequence[9]  = 0;
+  assign note_sequence[10] = N_As1;
+  assign note_sequence[11] = 0;
+  assign note_sequence[12] = N_As1;
+  assign note_sequence[13] = 0;
+  assign note_sequence[14] = N_As1;
+  assign note_sequence[15] = N_C2;
+
+  assign note = note_sequence[select];
+endmodule
+
+module pattern1_2(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  wire [3:0] note_sequence [15:0];
+
+  assign note_sequence[0]  = N_Gs1;
+  assign note_sequence[1]  = 0;
+  assign note_sequence[2]  = 0;
+  assign note_sequence[3]  = N_Gs1;
+  assign note_sequence[4]  = 0;
+  assign note_sequence[5]  = 0;
+  assign note_sequence[6]  = N_Gs1;
+  assign note_sequence[7]  = 0;
+  assign note_sequence[8]  = 0;
+  assign note_sequence[9]  = 0;
+  assign note_sequence[10] = N_Fs1;
+  assign note_sequence[11] = 0;
+  assign note_sequence[12] = N_Fs1;
+  assign note_sequence[13] = 0;
+  assign note_sequence[14] = N_Fs1;
+  assign note_sequence[15] = N_Gs1;
+
+  assign note = note_sequence[select];
+endmodule
+
+module pattern1_3(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  wire [3:0] note_sequence [15:0];
+
+  assign note_sequence[0]  = N_Gs1;
+  assign note_sequence[1]  = 0;
+  assign note_sequence[2]  = 0;
+  assign note_sequence[3]  = N_Gs1;
+  assign note_sequence[4]  = 0;
+  assign note_sequence[5]  = 0;
+  assign note_sequence[6]  = N_Gs1;
+  assign note_sequence[7]  = 0;
+  assign note_sequence[8]  = N_As1;
+  assign note_sequence[9]  = 0;
+  assign note_sequence[10] = 0;
+  assign note_sequence[11] = N_As1;
+  assign note_sequence[12] = 0;
+  assign note_sequence[13] = 0;
+  assign note_sequence[14] = N_B1;
+  assign note_sequence[15] = 0;
+
+  assign note = note_sequence[select];
+endmodule
+
+// Where is pattern2? It used to be a lower octave version of pattern1 but that
+// is now handled by bit shifting.
+
+module pattern3_1(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  assign note = N_C2;
+endmodule
+
+module pattern3_2(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  wire [3:0] note_sequence [15:0];
+
+  assign note_sequence[0] = N_C2;
+  assign note_sequence[1] = N_C2;
+  assign note_sequence[2] = N_C2;
+  assign note_sequence[3] = N_C2;
+  assign note_sequence[4] = N_C2;
+  assign note_sequence[5] = N_C2;
+  assign note_sequence[6] = N_C2;
+  assign note_sequence[7] = N_C2;
+  assign note_sequence[8] = N_C2;
+  assign note_sequence[9] = N_C2;
+  assign note_sequence[10] = N_C2;
+  assign note_sequence[11] = N_As1;
+  assign note_sequence[12] = N_As1;
+  assign note_sequence[13] = N_As1;
+  assign note_sequence[14] = N_A1;
+  assign note_sequence[15] = N_Gs1;
+
+  assign note = note_sequence[select];
+endmodule
+
+module pattern3_3(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  assign note = select <= 10 ? N_Gs1 : N_Fs1;
+endmodule
+
+module pattern3_4(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  wire [3:0] note_sequence [15:0];
+
+  assign note_sequence[0]  = N_Gs1;
+  assign note_sequence[1]  = N_Gs1; 
+  assign note_sequence[2]  = N_Gs1;
+  assign note_sequence[3]  = N_Gs1;
+  assign note_sequence[4]  = N_Gs1;
+  assign note_sequence[5]  = N_Gs1;
+  assign note_sequence[6]  = N_Fs1;
+  assign note_sequence[7]  = N_Fs1;
+  assign note_sequence[8]  = N_Gs1;
+  assign note_sequence[9]  = N_Gs1;
+  assign note_sequence[10] = N_Gs1;
+  assign note_sequence[11] = N_Fs1;
+  assign note_sequence[12] = N_Gs1;
+  assign note_sequence[13] = N_Gs1;
+  assign note_sequence[14] = N_As1;
+  assign note_sequence[15] = N_As1;
+
+  assign note = note_sequence[select];
+endmodule
+
+module pattern4_1(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  assign note = select[1:0] == 0 ? N_C4 : 0;
+endmodule
+
+module pattern4_2(
+  input wire [3:0] select,
+  output wire [3:0] note
+);
+  assign note = select[1:0] == 0 ? N_C4 : (select[1:0] == 2 ? N_B7 : 0);
+endmodule
+
+// verilator lint_on UNUSEDSIGNAL
+
+module pattern_selector(
+  input wire [3:0] pattern,
+  input wire [3:0] row,
+  output wire [11:0] freq
+);
+
+  wire [3:0] pnotes [15:0];
+
+  // Pattern 0 is empty
+  assign pnotes[0] = 0;
+
+  pattern1_1 p1(
+    .select(row),
+    .note(pnotes[1])
+  );
+
+  pattern1_2 p2(
+    .select(row),
+    .note(pnotes[2])
+  );
+
+  pattern1_3 p3(
+    .select(row),
+    .note(pnotes[3])
+  );
+
+  pattern3_1 p4(
+    .select(row),
+    .note(pnotes[4])
+  );
+
+  pattern3_2 p5(
+    .select(row),
+    .note(pnotes[5])
+  );
+
+  pattern3_3 p6(
+    .select(row),
+    .note(pnotes[6])
+  );
+
+  pattern3_4 p7(
+    .select(row),
+    .note(pnotes[7])
+  );
+
+  pattern4_1 p8(
+    .select(row),
+    .note(pnotes[8])
+  );
+
+  pattern4_2 p9(
+    .select(row),
+    .note(pnotes[9])
+  );
+
+  // All other patterns are unassigned
+  assign pnotes[10] = 0;
+  assign pnotes[11] = 0;
+  assign pnotes[12] = 0;
+  assign pnotes[13] = 0;
+  assign pnotes[14] = 0;
+  assign pnotes[15] = 0;
+
+  note_map nm1(
+    .select(pnotes[pattern]),
+    .freq(freq)
+  );
+
+endmodule
+
+module instrument0(
+  input wire [1:0] select,
+  output wire [1:0] vol
+);
+
+  wire [1:0] vol_sequence [3:0];
+
+  assign vol_sequence[0] = 2'b11;
+  assign vol_sequence[1] = 2'b11;
+  assign vol_sequence[2] = 2'b11;
+  assign vol_sequence[3] = 2'b00;
+
+  assign vol = vol_sequence[select];
+
+endmodule
+
+module instrument1(
+  input wire [1:0] select,
+  output wire [1:0] vol
+);
+
+  wire [1:0] vol_sequence [3:0];
+
+  assign vol_sequence[0] = 2'b10;
+  assign vol_sequence[1] = 2'b10;
+  assign vol_sequence[2] = 2'b01;
+  assign vol_sequence[3] = 2'b00;
+
+  assign vol = vol_sequence[select];
+
+endmodule
+
+module instrument2(
+  input wire [1:0] select,
+  output wire [1:0] vol
+);
+
+  wire [1:0] vol_sequence [3:0];
+
+  assign vol_sequence[0] = 2'b11;
+  assign vol_sequence[1] = 2'b01;
+  assign vol_sequence[2] = 2'b00;
+  assign vol_sequence[3] = 2'b00;
+
+  assign vol = vol_sequence[select];
+
+endmodule
+
+module sequencer(
+  input wire [1:0] tick_clock,
+  input wire [3:0] pattern_clock,
+  input wire rst_n,
+  output wire [11:0] freq0,
+  output wire [11:0] freq1,
+  output wire [11:0] freq2,
+  output wire [11:0] freq3,
+  output wire [1:0] vol0,
+  output wire [1:0] vol1,
+  output wire [1:0] vol2,
+  output wire [1:0] vol3
+);
+  wire [11:0] patterns [11:0];
+  wire [11:0] seq_freq [2:0];
+  wire [1:0] ivol [2:0];
+  wire seq_tick = !pattern_clock[3]; //(pattern_clock == 15);
+  reg [3:0] seq_clock;
+
+  assign patterns[0]  = 12'h001;
+  assign patterns[1]  = 12'h001;
+  assign patterns[2]  = 12'h002;
+  assign patterns[3]  = 12'h003;
+  assign patterns[4]  = 12'h841;
+  assign patterns[5]  = 12'h851;
+  assign patterns[6]  = 12'h862;
+  assign patterns[7]  = 12'h873;
+  assign patterns[8]  = 12'h941;
+  assign patterns[9]  = 12'h951;
+  assign patterns[10] = 12'h962;
+  assign patterns[11] = 12'h973;
+
+  // pattern selector 0 drives both channels 0 and 1
+  pattern_selector ps0(
+    .pattern(patterns[seq_clock][3:0]),
+    .row(pattern_clock),
+    .freq(seq_freq[0])
+  );
+
+  pattern_selector ps1(
+    .pattern(patterns[seq_clock][7:4]),
+    .row(pattern_clock),
+    .freq(seq_freq[1])
+  );
+
+  pattern_selector ps2(
+    .pattern(patterns[seq_clock][11:8]),
+    .row(pattern_clock),
+    .freq(seq_freq[2])
+  );
+
+  instrument0 i0(
+    .select(tick_clock),
+    .vol(ivol[0])
+  );
+
+  instrument1 i1(
+    .select(tick_clock),
+    .vol(ivol[1])
+  );
+
+  instrument2 i2(
+    .select(tick_clock),
+    .vol(ivol[2])
+  );
+
+  always @(posedge seq_tick, negedge rst_n) begin
+    if (!rst_n)
+      seq_clock <= 0;
+    else
+      if (seq_clock == 4'hB)
+        seq_clock <= 4'h8;
+      else
+        seq_clock <= seq_clock + 1;
+  end
+
+  assign freq0 = seq_freq[0];
+  assign freq1 = (seq_freq[0] >> 1); // channel 1 is just channel 0 shifted up one octave
+  assign freq2 = seq_freq[1];
+  assign freq3 = seq_freq[2];
+  assign vol0 = seq_freq[0] > 0 ? ivol[0] : 0;
+  assign vol1 = seq_freq[0] > 0 ? ivol[0] : 0;
+  assign vol2 = seq_freq[1] > 0 ? ivol[1] : 0;
+  assign vol3 = seq_freq[2] > 0 ? ivol[2] : 0;
+
+endmodule

diff --git a/src/config.json b/src/config.json
line changes: +3/-2
index 2c7c12e..8a7560e
--- a/src/config.json
+++ b/src/config.json
@@ -13,11 +13,12 @@
 
   "//": "PL_TARGET_DENSITY - You can increase this if Global Placement fails with error GPL-0302.",
   "//": "Users have reported that values up to 0.8 worked well for them.",
-  "PL_TARGET_DENSITY": 0.6,
+  "PL_TARGET_DENSITY": 0.65,
 
   "//": "CLOCK_PERIOD - Increase this in case you are getting setup time violations.",
   "//": "The value is in nanoseconds, so 20ns == 50MHz.",
-  "CLOCK_PERIOD": 20,
+  "//": "This design runs at 25.175 MHz",
+  "CLOCK_PERIOD": 39,
 
   "//": "Hold slack margin - Increase them in case you are getting hold violations.",
   "PL_RESIZER_HOLD_SLACK_MARGIN": 0.1,

diff --git a/src/iceshim.v b/src/iceshim.v
line changes: +45/-0
index 0000000..dcfe381
--- /dev/null
+++ b/src/iceshim.v
@@ -0,0 +1,45 @@
+/* This adapts the icestick I/O to work with the project module */
+
+`default_nettype none
+
+module iceshim(
+  input wire [7:0] port0,
+  output wire [7:0] port1,
+  inout wire [7:0] port2, // the 0th bit is stolen by rst, 1st bit is external clock
+  output wire [4:0] leds,
+  input wire clk
+);
+
+  wire [7:0] uio_in;
+  wire [7:0] uio_out;
+  wire [7:0] uio_oe;
+
+  //assign port2[0] = uio_oe[0] ? uio_out[0] : 1'bZ;
+  //assign port2[1] = uio_oe[1] ? uio_out[1] : 1'bZ;
+  assign port2[2] = uio_oe[2] ? uio_out[2] : 1'bZ;
+  assign port2[3] = uio_oe[3] ? uio_out[3] : 1'bZ;
+  assign port2[4] = uio_oe[4] ? uio_out[4] : 1'bZ;
+  assign port2[5] = uio_oe[5] ? uio_out[5] : 1'bZ;
+  assign port2[6] = uio_oe[6] ? uio_out[6] : 1'bZ;
+  assign port2[7] = uio_oe[7] ? uio_out[7] : 1'bZ;
+  //assign uio_in[0] = port2[0];
+  //assign uio_in[1] = port2[1];
+  assign uio_in[2] = port2[2];
+  assign uio_in[3] = port2[3];
+  assign uio_in[4] = port2[4];
+  assign uio_in[5] = port2[5];
+  assign uio_in[6] = port2[6];
+  assign uio_in[7] = port2[7];
+  assign leds[2:0] = uio_out[6:4];
+
+  tt_um_bytex64_munch user_module(
+    .ui_in(port0),
+    .uo_out(port1),
+    .uio_in(uio_in),
+    .uio_out(uio_out),
+    .uio_oe(uio_oe),
+    .ena(1),
+    .clk(port2[1]),
+    .rst_n(port2[0])
+  );
+endmodule

diff --git a/src/project.v b/src/project.v
line changes: +221/-9
index 70d57f5..5270da1
--- a/src/project.v
+++ b/src/project.v
@@ -5,6 +5,56 @@
 
 `default_nettype none
 
+module clock_generator(
+  input wire clk,                  // 25.175MHz main clock
+  input wire rst_n,                // active low reset
+  output wire [5:0] pwm_clock,     // 393kHz PWM clock (main / 64)
+  input wire vsync,                // ~60Hz video clock
+  output wire [1:0] atick_clock,   // audio tick clock state, used for volume modulation
+  output wire [3:0] pattern_clock  // pattern clock, increments with audio_tick
+);
+  reg [5:0] r_pwm_clock;
+  reg [2:0] r_atick_clock;
+  reg [3:0] r_pattern_clock;
+
+  always @(posedge clk, negedge rst_n) begin
+    if (!rst_n)
+      r_pwm_clock <= 0;
+    else
+      r_pwm_clock <= r_pwm_clock + 1;
+  end
+  
+  assign pwm_clock = r_pwm_clock;
+
+  always @(posedge vsync, negedge rst_n) begin
+    if (!rst_n)
+      r_atick_clock <= 0;
+    else begin
+      // Divide vsync by 6 to get 10Hz sequencer clock
+      // 10Hz = 600 ticks per minute
+      // four ticks per beat
+      // 150 BPM
+      if (r_atick_clock == 5)
+        r_atick_clock <= 0;
+      else
+        r_atick_clock <= r_atick_clock + 1;
+    end
+  end
+
+  assign atick_clock = r_atick_clock[2:1];
+  wire audio_tick = (r_atick_clock == 0);
+
+  always @(posedge audio_tick, negedge rst_n) begin
+    if (!rst_n)
+      r_pattern_clock <= 0;
+    else
+      r_pattern_clock <= r_pattern_clock + 1;
+  end
+
+  assign pattern_clock = r_pattern_clock;
+
+endmodule
+
 module tt_um_bytex64_munch (
     input  wire [7:0] ui_in,    // Dedicated inputs
     output wire [7:0] uo_out,   // Dedicated outputs
@@ -16,6 +66,7 @@ module tt_um_bytex64_munch (
     input  wire       rst_n     // reset_n - low to reset
 );
 
+  // Video timing/output signals
   wire [9:0] hpos;
   wire [9:0] vpos;
   wire hsync, vsync;
@@ -24,25 +75,186 @@ module tt_um_bytex64_munch (
   wire [1:0] G;
   wire [1:0] B;
 
-  assign uo_out  = {hsync, B[0], G[0], R[0], vsync, B[1], G[1], R[1]};
-  assign uio_out = 0;
-  assign uio_oe  = 0;
+  // Video generation signals
+  wire [5:0] layer [1:0];
+  wire [5:0] pixel_color;
+  wire [2:0] dither;
+  wire [1:0] bright [7:0];  // 8 2-bit brightness levels
+  wire [5:0] palette [7:0]; // 8 6-bit colors
+  wire [2:0] munch_level;   // brightness output for munch module
+  wire text_pixel;          // on/off output for text module
+
+  // Audio signals
+  wire audio;               // Audio output (~200kHz PWM)
+  wire [5:0] pwm_clock;
+  wire [1:0] atick_clock;
+  wire [3:0] pattern_clock;
+
+  // Misc signals
+  wire [5:0] lfsr;
+  wire [1:0] stage;
+
+  // State
+  reg [6:0] counter;        // general 7-bit counter
+
+  clock_generator clock_gen(
+    .clk(clk),
+    .rst_n(rst_n),
+    .pwm_clock(pwm_clock),
+    .vsync(vsync),
+    .atick_clock(atick_clock),
+    .pattern_clock(pattern_clock)
+  );
+
+  lfsr lfsr_dev(
+    .clk(clk),
+    .rst_n(rst_n),
+    .bits(lfsr)
+  );
+
+  audio audio_mod(
+    .pwm_clock(pwm_clock),
+    .atick_clock(atick_clock),
+    .pattern_clock(pattern_clock),
+    .rst_n(rst_n),
+    .rng(lfsr[0]),
+    .audio(audio)
+  );
 
   hvsync_generator hvsync_gen(
     .clk(clk),
-    .reset(~rst_n),
+    .reset(!rst_n),
     .hsync(hsync),
     .vsync(vsync),
     .display_on(display_on),
     .hpos(hpos),
-    .vpos(hpos)
+    .vpos(vpos)
   );
 
-  assign R = display_on ? hpos[3:2] : 2'b00;
-  assign G = display_on ? hpos[3:2] : 2'b00;
-  assign B = display_on ? hpos[3:2] : 2'b00;
+  assign palette[0] = 6'b000000;
+  assign palette[1] = 6'b110000;
+  assign palette[2] = 6'b001100;
+  assign palette[3] = 6'b000011;
+  assign palette[4] = 6'b001111;
+  assign palette[5] = 6'b110011;
+  assign palette[6] = 6'b111100;
+  assign palette[7] = 6'b111111;
+
+  assign dither[0] = lfsr[4] & lfsr[5]; // 1/4
+  assign dither[1] = lfsr[3];           // 1/2
+  assign dither[2] = lfsr[1] | lfsr[2]; // 3/4
+/*
+  assign dither[0] = hpos[0] & vpos[0];
+  assign dither[1] = hpos[0] ^ vpos[0];
+  assign dither[2] = hpos[0] | vpos[0];
+*/
+
+  assign bright[0] = 2'b00;
+  assign bright[1] = {1'b0, dither[0]};
+  assign bright[2] = {1'b0, dither[1]};
+  assign bright[3] = {1'b0, dither[2]};
+  assign bright[4] = 2'b01;
+  assign bright[5] = {dither[0], dither[2]};
+  assign bright[6] = 2'b10;
+  assign bright[7] = 2'b11;
+
+  munch munch_gen(
+    .counter(counter),
+    .hpos(hpos[8:2]),
+    .vpos(vpos[8:2]),
+    .level(munch_level)
+  );
+
+  text_sequencer text_gen(
+    .selector(pattern_clock[3:2]),
+    .hpos(hpos),
+    .vpos(vpos),
+    .pixel(text_pixel)
+  );
+
+  stage_sequencer stage_seq_inst(
+    .seq_clk(!pattern_clock[3]),
+    .rst_n(rst_n),
+    .stage(stage)
+  );
+
+  wire [2:0] munch_color = {1'b0, stage}; //stage == 0 ? 0 : (stage == 1 ? 2 : lfsr[2:0]);
+
+  assign layer[0] = palette[munch_color] & {3{bright[munch_level]}};
+  // color bars
+  //assign layer[0] = palette[vpos[6:4]] & {3{bright[hpos[6:4]]}};
+  assign layer[1] = text_pixel ? palette[7] : 0;
+
+  assign pixel_color = layer[1] != 0 ? layer[1] : layer[0];
+
+  always @(posedge vsync, negedge rst_n) begin
+    if (!rst_n)
+      counter <= 0;
+    else
+      counter <= counter + 1;
+  end
+
+  assign R = display_on ? pixel_color[5:4] : 2'b00;
+  assign G = display_on ? pixel_color[3:2] : 2'b00;
+  assign B = display_on ? pixel_color[1:0] : 2'b00;
+
+  assign uo_out  = {!hsync, B[0], G[0], R[0], !vsync, B[1], G[1], R[1]};
+  assign uio_out = {audio, pattern_clock[3:1], 4'b0};
+  assign uio_oe  = 8'b11110000;
 
   // List all unused inputs to prevent warnings
-  wire _unused = &{ena, clk, rst_n, 1'b0};
+  wire _unused = &{ena, ui_in, uio_in, 1'b0};
+endmodule
+
+module lfsr(
+  input wire clk,
+  input wire rst_n,
+  output wire [5:0] bits
+);
+  reg [10:0] lfsr;          // LFSR
+
+  always @(posedge clk, negedge rst_n) begin
+    if (!rst_n)
+      lfsr <= 11'h0;
+    else
+      lfsr <= {lfsr[0] ~^ lfsr[2], lfsr[10:1]};
+  end
+
+  assign bits = lfsr[5:0];
+endmodule
+
+module stage_sequencer(
+  input wire seq_clk,
+  input wire rst_n,
+  output wire [1:0] stage
+);
+  reg [3:0] timer;
+  reg [1:0] stage_seq;
+
+  wire [3:0] stage_timings [3:0];
+
+  assign stage_timings[0] = 3;
+  assign stage_timings[1] = 7;
+  assign stage_timings[2] = 7;
+  assign stage_timings[3] = 0;
+
+  always @(posedge seq_clk, negedge rst_n) begin
+    if (!rst_n) begin
+      stage_seq <= 0;
+      timer <= stage_timings[0];
+    end
+    else begin
+      if (stage_timings[stage_seq] != 0) begin
+        if (timer == 0) begin
+          stage_seq <= stage_seq + 1;
+          timer <= stage_timings[stage_seq + 1];
+        end
+        else
+          timer <= timer - 1;
+      end
+    end
+  end
+
+  assign stage = stage_seq;
 
 endmodule

diff --git a/src/video.v b/src/video.v
line changes: +142/-0
index 0000000..4bec5ea
--- /dev/null
+++ b/src/video.v
@@ -0,0 +1,142 @@
+module munch(
+  input wire [6:0] counter,
+  input wire [6:0] hpos,
+  input wire [6:0] vpos,
+  output wire [2:0] level
+);
+  wire [6:0] munch;
+  assign munch[0] = (vpos ^ (counter - 0)) == hpos;
+  assign munch[1] = (vpos ^ (counter - 1)) == hpos;
+  assign munch[2] = (vpos ^ (counter - 2)) == hpos;
+  assign munch[3] = (vpos ^ (counter - 3)) == hpos;
+  assign munch[4] = (vpos ^ (counter - 4)) == hpos;
+  assign munch[5] = (vpos ^ (counter - 5)) == hpos;
+  assign munch[6] = (vpos ^ (counter - 6)) == hpos;
+
+  assign level = (munch[0] ? 7 :
+    (munch[1] ? 6 :
+     (munch[2] ? 5 :
+      (munch[3] ? 4 :
+       (munch[4] ? 3 :
+        (munch[5] ? 2 :
+         (munch[6] ? 1 :
+          0)))))));
+endmodule
+
+module chargen(
+  input wire [9:0] x,
+  input wire [9:0] y,
+  input wire [3:0] character,
+  input wire [9:0] hpos,
+  input wire [9:0] vpos,
+  output wire pixel
+);
+  wire [9:0] charmap [11:0];
+  wire [9:0] segments;
+
+  /* digits 0-9
+  assign charmap[0] = 10'b0000111111;
+  assign charmap[1] = 10'b0000000110;
+  assign charmap[2] = 10'b0001011011;
+  assign charmap[3] = 10'b0001001111;
+  assign charmap[4] = 10'b0001100110;
+  assign charmap[5] = 10'b0001101101;
+  assign charmap[6] = 10'b0001111101;
+  assign charmap[7] = 10'b0000000111;
+  assign charmap[8] = 10'b0001111111;
+  assign charmap[9] = 10'b0001101111;
+  */
+  assign charmap[0]  = 10'b0000000000;  // space
+  assign charmap[1]  = 10'b0001110111;  // A
+  assign charmap[2]  = 10'b0000111001;  // C
+  assign charmap[3]  = 10'b0001111001;  // E
+  assign charmap[4]  = 10'b0001110110;  // H
+  assign charmap[5]  = 10'b0110110000;  // K
+  assign charmap[6]  = 10'b0000111000;  // L
+  assign charmap[7]  = 10'b0001110011;  // P
+  assign charmap[8]  = 10'b0101110011;  // R
+  assign charmap[9]  = 10'b0001101101;  // S
+  assign charmap[10] = 10'b1000000001;  // T
+  assign charmap[11] = 10'b1000011111;  // V (U)
+
+  assign segments = charmap[character];
+
+  assign pixel = (segments[0] && hpos >= x && hpos <= x + 50 && vpos == y)
+               | (segments[1] && hpos == x + 50 && vpos >= y && vpos <= y + 50)
+               | (segments[2] && hpos == x + 50 && vpos >= y + 50 && vpos <= y + 100)
+               | (segments[3] && hpos >= x && hpos <= x + 50 && vpos == y + 100)
+               | (segments[4] && hpos == x && vpos >= y + 50 && vpos <= y + 100)
+               | (segments[5] && hpos == x && vpos >= y && vpos <= y + 50)
+               | (segments[6] && hpos >= x && hpos <= x + 50 && vpos == y + 50)
+               | (segments[7] && hpos >= x && hpos <= x + 50 && vpos >= y && vpos <= y + 50
+                  && (y - vpos) == (hpos - (x + 50)))
+               | (segments[8] && hpos >= x && hpos <= x + 50 && vpos >= y + 50 && vpos <= y + 100
+                  && ((y + 50) - vpos) == (x - hpos))
+               | (segments[9] && hpos == x + 25 && vpos >= y && vpos <= y + 100);
+endmodule
+
+parameter C_sp = 4'd0;
+parameter C_A  = 4'd1;
+parameter C_C  = 4'd2;
+parameter C_E  = 4'd3;
+parameter C_H  = 4'd4;
+parameter C_K  = 4'd5;
+parameter C_L  = 4'd6;
+parameter C_P  = 4'd7;
+parameter C_R  = 4'd8;
+parameter C_S  = 4'd9;
+parameter C_T  = 4'd10;
+parameter C_V  = 4'd11;
+
+module text(
+  input wire [9:0] x,
+  input wire [9:0] y,
+  input wire [23:0] str,
+  input wire [9:0] hpos,
+  input wire [9:0] vpos,
+  output wire pixel
+);
+  wire [3:0] str_chars [5:0];
+  wire [2:0] h_dist = (hpos - x) >> 6;
+  wire [2:0] char_idx = h_dist;
+  wire chargen_pixel;
+  assign {
+    str_chars[5],  str_chars[4], str_chars[3],  str_chars[2],
+    str_chars[1],  str_chars[0]
+  } = str;
+
+  chargen chargen(
+    .x(x + char_idx * 64),
+    .y(y),
+    .character(str_chars[char_idx]),
+    .hpos(hpos),
+    .vpos(vpos),
+    .pixel(chargen_pixel)
+  );
+
+  assign pixel = hpos >= x && hpos < x + (6 * 64) ? chargen_pixel : 0;
+endmodule
+
+module text_sequencer(
+  input wire [1:0] selector,
+  input wire [9:0] hpos,
+  input wire [9:0] vpos,
+  output wire pixel
+);
+
+  wire [23:0] words [3:0];
+
+  assign words[0] = {C_sp, C_sp, C_sp, C_T, C_A, C_E};
+  assign words[1] = {C_sp, C_P, C_E, C_E, C_L, C_S};
+  assign words[2] = {C_sp, C_sp, C_K, C_C, C_A, C_H};
+  assign words[3] = {C_T, C_A, C_E, C_P, C_E, C_R};
+
+  text text_gen(
+    .x(100),
+    .y(280),
+    .str(words[selector]),
+    .hpos(hpos),
+    .vpos(vpos),
+    .pixel(pixel)
+  );
+endmodule

diff --git a/test/Makefile b/test/Makefile
line changes: +1/-1
index b9b0b93..fe3c3a8
--- a/test/Makefile
+++ b/test/Makefile
@@ -5,7 +5,7 @@
 SIM ?= icarus
 TOPLEVEL_LANG ?= verilog
 SRC_DIR = $(PWD)/../src
-PROJECT_SOURCES = hvsync_generator.v project.v
+PROJECT_SOURCES = hvsync_generator.v audio.v video.v project.v
 
 ifneq ($(GATES),yes)
 

diff --git a/test/Makefile.audio b/test/Makefile.audio
line changes: +41/-0
index 0000000..321e73c
--- /dev/null
+++ b/test/Makefile.audio
@@ -0,0 +1,41 @@
+# Makefile
+# See https://docs.cocotb.org/en/stable/quickstart.html for more info
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+SRC_DIR = $(PWD)/../src
+PROJECT_SOURCES = hvsync_generator.v audio.v video.v project.v
+
+ifneq ($(GATES),yes)
+
+# RTL simulation:
+SIM_BUILD				= sim_build/rtl
+VERILOG_SOURCES += $(addprefix $(SRC_DIR)/,$(PROJECT_SOURCES))
+COMPILE_ARGS 		+= -I$(SRC_DIR)
+
+else
+
+# Gate level simulation:
+SIM_BUILD				= sim_build/gl
+COMPILE_ARGS    += -DGL_TEST
+COMPILE_ARGS    += -DFUNCTIONAL
+COMPILE_ARGS    += -DUSE_POWER_PINS
+COMPILE_ARGS    += -DSIM
+COMPILE_ARGS    += -DUNIT_DELAY=\#1
+VERILOG_SOURCES += $(PDK_ROOT)/sky130A/libs.ref/sky130_fd_sc_hd/verilog/primitives.v
+VERILOG_SOURCES += $(PDK_ROOT)/sky130A/libs.ref/sky130_fd_sc_hd/verilog/sky130_fd_sc_hd.v
+
+# this gets copied in by the GDS action workflow
+VERILOG_SOURCES += $(PWD)/gate_level_netlist.v
+
+endif
+
+VERILOG_SOURCES += $(PWD)/tb_audio.v
+TOPLEVEL = tb_audio
+
+# MODULE is the basename of the Python test file
+MODULE = test_audio
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim

diff --git a/test/tb.gtkw b/test/tb.gtkw
line changes: +14/-28
index c92ca3c..d5e3954
--- a/test/tb.gtkw
+++ b/test/tb.gtkw
@@ -1,39 +1,25 @@
 [*]
 [*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
-[*] Mon Nov 20 16:00:28 2023
+[*] Wed Aug 28 04:41:58 2024
 [*]
-[dumpfile] "/home/uri/p/tt-new-template-proto/test/tb.vcd"
-[dumpfile_mtime] "Mon Nov 20 15:58:34 2023"
-[dumpfile_size] 1110
-[savefile] "/home/uri/p/tt-new-template-proto/test/tb.gtkw"
+[dumpfile] "/home/chip/projects/tinytapeout/munch/test/tb.vcd"
+[dumpfile_mtime] "Wed Aug 28 04:34:55 2024"
+[dumpfile_size] 2596520
+[savefile] "/home/chip/projects/tinytapeout/munch/test/tb.gtkw"
 [timestart] 0
-[size] 1376 600
-[pos] -1 -1
-*-24.534533 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
+[size] 1711 925
+[pos] 37 -2
+*-37.534531 30000001 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
 [treeopen] tb.
 [sst_width] 297
 [signals_width] 230
 [sst_expanded] 1
-[sst_vpaned_height] 158
+[sst_vpaned_height] 270
 @28
-tb.user_project.ena
-@29
-tb.user_project.clk
-@28
-tb.user_project.rst_n
-@200
--Inputs
-@22
-tb.user_project.ui_in[7:0]
-@200
--Bidirectional Pins
-@22
-tb.user_project.uio_in[7:0]
-tb.user_project.uio_oe[7:0]
-tb.user_project.uio_out[7:0]
-@200
--Output Pins
-@22
-tb.user_project.uo_out[7:0]
+tb.clock_gen.vsync
+tb.clock_gen.r_atick_clock[2:0]
+tb.clock_gen.audio_tick
+@23
+tb.clock_gen.r_pattern_clock[3:0]
 [pattern_trace] 1
 [pattern_trace] 0

diff --git a/test/tb.v b/test/tb.v
line changes: +9/-23
index 0d9eea5..ac70cdb
--- a/test/tb.v
+++ b/test/tb.v
@@ -1,9 +1,6 @@
 `default_nettype none
 `timescale 1ns / 1ps
 
-/* This testbench just instantiates the module and makes some convenient wires
-   that can be driven / tested by the cocotb test.py.
-*/
 module tb ();
 
   // Dump the signals to a VCD file. You can view it with gtkwave.
@@ -16,30 +13,19 @@ module tb ();
   // Wire up the inputs and outputs:
   reg clk;
   reg rst_n;
-  reg ena;
-  reg [7:0] ui_in;
-  reg [7:0] uio_in;
   wire [7:0] uo_out;
   wire [7:0] uio_out;
   wire [7:0] uio_oe;
 
-  // Replace tt_um_example with your module name:
-  tt_um_bytex64_munch user_project (
-
-      // Include power ports for the Gate Level test:
-`ifdef GL_TEST
-      .VPWR(1'b1),
-      .VGND(1'b0),
-`endif
-
-      .ui_in  (ui_in),    // Dedicated inputs
-      .uo_out (uo_out),   // Dedicated outputs
-      .uio_in (uio_in),   // IOs: Input path
-      .uio_out(uio_out),  // IOs: Output path
-      .uio_oe (uio_oe),   // IOs: Enable path (active high: 0=input, 1=output)
-      .ena    (ena),      // enable - goes high when design is selected
-      .clk    (clk),      // clock
-      .rst_n  (rst_n)     // not reset
+  tt_um_bytex64_munch munch(
+    .clk(clk),
+    .rst_n(rst_n),
+    .ena(1'b1),
+    .ui_in(8'b0),
+    .uo_out(uo_out),
+    .uio_in(8'b0),
+    .uio_out(uio_out),
+    .uio_oe(uio_oe)
   );
 
 endmodule

diff --git a/test/tb_audio.gtkw b/test/tb_audio.gtkw
line changes: +57/-0
index 0000000..34536bc
--- /dev/null
+++ b/test/tb_audio.gtkw
@@ -0,0 +1,57 @@
+[*]
+[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
+[*] Wed Aug 28 06:34:28 2024
+[*]
+[dumpfile] "/home/chip/projects/tinytapeout/munch/test/tb_audio.vcd"
+[dumpfile_mtime] "Wed Aug 28 06:34:21 2024"
+[dumpfile_size] 904873
+[savefile] "/home/chip/projects/tinytapeout/munch/test/tb_audio.gtkw"
+[timestart] 0
+[size] 1709 842
+[pos] -1 -1
+*-38.000000 400000000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
+[treeopen] tb_audio.
+[sst_width] 276
+[signals_width] 298
+[sst_expanded] 1
+[sst_vpaned_height] 242
+@28
+tb_audio.vsync
+tb_audio.rst_n
+tb_audio.clock_gen.audio_tick
+@22
+tb_audio.clock_gen.r_atick_clock[2:0]
+tb_audio.pattern_clock[3:0]
+@200
+-Sequencer
+@22
+tb_audio.seq.seq_clock[3:0]
+@200
+-Channel 0
+@24
+tb_audio.seq.freq0[11:0]
+tb_audio.seq.vol0[1:0]
+@200
+-Channel 1
+@24
+tb_audio.seq.freq1[11:0]
+tb_audio.seq.vol1[1:0]
+@200
+-Channel 2
+@24
+tb_audio.seq.freq2[11:0]
+tb_audio.seq.vol2[1:0]
+@200
+-Channel 3
+@24
+tb_audio.seq.freq3[11:0]
+tb_audio.seq.vol3[1:0]
+@200
+-Stage Sequencer
+@28
+tb_audio.stage_seq_inst.seq_clk
+@24
+tb_audio.stage_seq_inst.stage[1:0]
+tb_audio.stage_seq_inst.timer[3:0]
+[pattern_trace] 1
+[pattern_trace] 0

diff --git a/test/tb_audio.v b/test/tb_audio.v
line changes: +48/-0
index 0000000..bda05a4
--- /dev/null
+++ b/test/tb_audio.v
@@ -0,0 +1,48 @@
+module tb_audio ();
+  initial begin
+    $dumpfile("tb_audio.vcd");
+    $dumpvars(0, tb_audio);
+    #1;
+  end
+
+  reg vsync;
+  reg rst_n;
+  wire [5:0] pwm_clock;
+  wire [1:0] atick_clock;
+  wire [3:0] pattern_clock;
+
+  clock_generator clock_gen(
+    .clk(0),
+    .rst_n(rst_n),
+    .pwm_clock(pwm_clock),
+    .vsync(vsync),
+    .atick_clock(atick_clock),
+    .pattern_clock(pattern_clock)
+  );
+
+  wire [11:0] freq0, freq1, freq2, freq3;
+  wire [1:0] vol0, vol1, vol2, vol3;
+
+  sequencer seq(
+    .tick_clock(atick_clock),
+    .pattern_clock(pattern_clock),
+    .rst_n(rst_n),
+    .freq0(freq0),
+    .freq1(freq1),
+    .freq2(freq2),
+    .freq3(freq3),
+    .vol0(vol0),
+    .vol1(vol1),
+    .vol2(vol2),
+    .vol3(vol3)
+  );
+
+  wire [2:0] stage;
+
+  stage_sequencer stage_seq_inst(
+    .seq_clk(!pattern_clock[3]),
+    .rst_n(rst_n),
+    .stage(stage)
+  );
+
+endmodule

diff --git a/test/test.py b/test/test.py
line changes: +20/-18
index 03d8548..2d4f64d
--- a/test/test.py
+++ b/test/test.py
@@ -5,31 +5,33 @@ import cocotb
 from cocotb.clock import Clock
 from cocotb.triggers import ClockCycles, Timer
 
+async def reset(dut):
+    # Reset
+    dut._log.info("Reset")
+    dut.rst_n.value = 1
+    await ClockCycles(dut.clk, 1);
+    dut.rst_n.value = 0
+    await ClockCycles(dut.clk, 1);
+    dut.rst_n.value = 1
 
 @cocotb.test()
-async def test_project(dut):
+async def test_lfsr(dut):
     dut._log.info("Start")
 
-    # Set the clock period to 10 us (100 KHz)
-    clock = Clock(dut.clk, 10, units="us")
+    # Set the clock period to 40ns (25MHz)
+    # Actual clock is 25.175 (39.722ns) but whatever
+    clock = Clock(dut.clk, 40, units="ns")
     cocotb.start_soon(clock.start())
 
-    await Timer(5, "us")
-    dut.ena.value = 1
-    await Timer(5, "us")
+    await reset(dut)
 
-    # Reset
-    dut._log.info("Reset")
-    dut.rst_n.value = 1
-    await Timer(5, "us")
-    dut.rst_n.value = 0
-    await Timer(10, "us")
-    dut.ui_in.value = 0
-    dut.uio_in.value = 0
-    dut.rst_n.value = 1
+    dut._log.info("Test LFSR module")
 
-    dut._log.info("Test project behavior")
+    await ClockCycles(dut.clk, 1)
 
-    await ClockCycles(dut.clk, 20)
+    for i in range(1, 2**11 - 1):
+        await ClockCycles(dut.clk, 1)
+        assert dut.munch.lfsr_dev.lfsr.value != 0
 
-    assert dut.uo_out.value == 3
+    await ClockCycles(dut.clk, 1)
+    assert dut.munch.lfsr_dev.lfsr.value == 0

diff --git a/test/test_audio.py b/test/test_audio.py
line changes: +28/-0
index 0000000..223cf08
--- /dev/null
+++ b/test/test_audio.py
@@ -0,0 +1,28 @@
+# SPDX-FileCopyrightText: © 2024 Tiny Tapeout
+# SPDX-License-Identifier: Apache-2.0
+
+import cocotb
+from cocotb.clock import Clock
+from cocotb.triggers import ClockCycles, Timer
+
+async def reset(dut):
+    # Reset
+    dut._log.info("Reset")
+    dut.rst_n.value = 1
+    await ClockCycles(dut.vsync, 1);
+    dut.rst_n.value = 0
+    await ClockCycles(dut.vsync, 1);
+    dut.rst_n.value = 1
+
+@cocotb.test()
+async def test_audio_clock(dut):
+    dut._log.info("Start")
+
+    # Set the clock period to 10 us (100 KHz)
+    clock = Clock(dut.vsync, 16, units="ms")
+    cocotb.start_soon(clock.start())
+
+    await reset(dut)
+
+    dut._log.info("Test Audio Clock")
+    await ClockCycles(dut.clock_gen.audio_tick, 1024)