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.
The Hookpad → SystemVerilog Converter
To streamline the workflow, I built a browser-based converter that takes a Hookpad JSON file and produces two ready-to-synthesize files:
- ROM module – stores note timing and frequency dividers.
- Top player module – instantiates the ROM and drives the
AUD_PWM
/AUD_SD
pins on the Nexys A7.
👉 Try the tool here: /hookpad-to-verilog
How it works
-
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.
-
Generate SystemVerilog modules
- A ROM with a case statement containing all entries.
- A top player that steps through the ROM and toggles the audio output.
Example ROM Module
// 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
Example Top Module (ROM Player)
// 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
How to use the converter
- Go to the converter page.
- Upload your Hookpad JSON file or paste the JSON.
- Enter the tempo, key, and mode.
- Click Generate Verilog.
- Download both the ROM and TOP
.sv
files. - Add them to your Vivado project, mark them as SystemVerilog, and connect
AUD_PWM
/AUD_SD
in the Nexys A7 XDC constraints. - Synthesize, program, and listen!