+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 $@
+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
+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
# 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.
-SOURCES = hvsync_generator.v project.v
-
-munch.vvp: $(SOURCES)
- iverilog -o $@ $(SOURCES)
+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
"//": "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,
+/* 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
`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
input wire rst_n // reset_n - low to reset
);
+ // Video timing/output signals
wire [9:0] hpos;
wire [9:0] vpos;
wire hsync, vsync;
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
+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
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)
+# 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
[*]
[*] 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
`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.
// 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
+[*]
+[*] 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
+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
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
+# 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)