Programming music directly into an FPGA can feel like a grind—especially if you’re hand-coding note tables and timing values. When working with the Digilent Nexys A7 (100 MHz), I wanted a more direct way to take melodies I’d written and make the board “sing” without endless trial and error.
That’s where Hookpad comes in. Hookpad is a web-based music sketchpad created by Hooktheory. It lets you build chord progressions and melodies quickly, and then export them in a structured JSON format. This JSON contains all the information about notes, durations, and rests—perfect raw material for generating Verilog code.
To streamline the workflow, I built a browser-based converter that takes a Hookpad JSON file and produces two ready-to-synthesize files:
AUD_PWM / AUD_SD pins on the Nexys A7.👉 Try the tool here: /hookpad-to-verilog
Load a Hookpad JSON file
You can upload the file directly or paste its contents into the converter.
The tool parses the note arrays and normalizes beats, rests, and durations.
Build entries
Each note is converted into:
div_val — a clock divider to generate the square wave frequency. dur_ticks — the number of 100 MHz clock cycles for the note’s duration.// Auto-generated monophonic ROM from Hookpad JSON
module hookpad_rom_song(
input wire clk,
input wire [11:0] addr,
output reg [31:0] div_val,
output reg [31:0] dur_ticks,
output wire valid
);
localparam integer NOTE_COUNT = 4;
assign valid = 1'b1;
wire [11:0] addr_eff = addr;
always @(posedge clk) begin
case (addr_eff)
12'd0: begin div_val <= 32'd191113; dur_ticks <= 32'd10000000; end
12'd1: begin div_val <= 32'd170262; dur_ticks <= 32'd10000000; end
12'd2: begin div_val <= 32'd151686; dur_ticks <= 32'd10000000; end
12'd3: begin div_val <= 32'd143173; dur_ticks <= 32'd10000000; end
default: begin div_val <= 32'd0; dur_ticks <= 32'd10_000_000; end
endcase
end
endmodule
// Drop-in monophonic top for Nexys A7 (100 MHz)
module top_hookpad_rom_song_mono #(
parameter integer CLK_HZ = 100_000_000
)(
input wire CLK100MHZ,
output wire AUD_SD,
output reg AUD_PWM
);
assign AUD_SD = 1'b1; // enable amp
// ROM instance
wire [31:0] div_val;
wire [31:0] dur_ticks;
reg [11:0] rom_addr = 12'd0;
hookpad_rom_song rom_i (
.clk (CLK100MHZ),
.addr (rom_addr),
.div_val (div_val),
.dur_ticks (dur_ticks),
.valid ()
);
// Tone generator
reg [31:0] cnt = 0;
reg tone_hi = 0;
reg [31:0] dur_cnt = 0;
always @(posedge CLK100MHZ) begin
if (dur_cnt >= dur_ticks) begin
dur_cnt <= 0;
rom_addr <= rom_addr + 1;
cnt <= 0;
tone_hi <= 0;
end else begin
dur_cnt <= dur_cnt + 1;
end
if (div_val == 0) begin
cnt <= 0;
tone_hi <= 0;
end else if (cnt >= div_val) begin
cnt <= 0;
tone_hi <= ~tone_hi;
end else begin
cnt <= cnt + 1;
end
AUD_PWM <= tone_hi;
end
endmodule
.sv files. AUD_PWM / AUD_SD in the Nexys A7 XDC constraints.