Over the past few days I’ve been working on a fun FPGA project: getting a sprite (my little ship) to move around the screen on a Nexys A7 board, complete with a starfield background, rotation, and even thruster sounds when you press the up or down buttons.
This post walks through the design and shares the SystemVerilog code that made it happen.
VGA Setup
The Nexys A7’s 100 MHz oscillator is run through a Vivado Clocking Wizard IP to generate a 65 MHz pixel clock. This drives the VGA controller module that generates 1024×768 @ 60 Hz timing.
clk65_gen u_clk (
.clk_in1 (CLK100MHZ),
.reset (1'b0),
.clk_out1(clk_pix),
.locked (pll_locked)
);
The VGA module outputs x
, y
pixel coordinates and asserts an active
flag when inside the visible area. These signals drive all drawing logic.
Button Handling
Buttons are notoriously noisy, so each one goes through a 2-flip-flop synchronizer. I also track the previous state to generate one-shot pulses on rising edges, which makes rotation and thrusting easier to control.
reg bL_s1, bL_s2, bL_prev;
always @(posedge clk_pix) begin
bL_s1 <= BTNL;
bL_s2 <= bL_s1;
bL_prev <= bL_s2;
end
wire pL_edge = bL_s2 & ~bL_prev; // one-shot pulse
I use pulses for quick taps, and the level (held state) to allow continuous rotation and movement while holding the button.
Sprite Orientation and Movement
The sprite is 32×32 pixels. I track its (x,y)
position and a 2-bit orient
value. Orientation increments counter-clockwise:
0 = up
1 = left
2 = down
3 = right
At each step tick (≈8 ms), orientation is updated if you press left or right, and (dx,dy)
is computed from the new orientation. Then if up is pressed, the sprite moves forward; if down is pressed, it moves backward.
unique case (orient_next)
2'd0: begin dx = 0; dy = -SPEED; end // up
2'd1: begin dx = -SPEED; dy = 0; end // left
2'd2: begin dx = 0; dy = SPEED; end // down
2'd3: begin dx = SPEED; dy = 0; end // right
endcase
The sprite always moves in the direction its “top” is facing, which makes flying it around feel natural.
Starfield Background
To give the ship something to fly through, I added a starfield generator. It simply decides (based on coordinates and some bit-mixing) whether a given pixel should light up as a star. The stars are white, and the rest of the background is black.
Sprite Rendering
The ship mask is stored as a 32×32 bitmap. The sprite_mask32
module indexes into it using (sx, sy)
relative coordinates, rotated by the orient
value. White pixels are drawn as opaque, while black pixels are treated as transparent.
sprite_mask32 u_mask (
.sx (sx),
.sy (sy),
.orient (orient),
.bit_on (spr_opaque)
);
Thruster Sound
For fun, I added audio output. When the up or down button is pressed, an LFSR (linear feedback shift register) generates pseudo-random noise. The MSB is taken as a crude low-pass filter, and this drives a PWM pin for sound. It sounds like a hiss — perfect for thrusters!
// 16-bit LFSR noise
always @(posedge clk_pix) begin
lfsr <= {lfsr[14:0],
lfsr[15] ^ lfsr[13] ^ lfsr[12] ^ lfsr[10]};
noise_bit <= lfsr[15];
end
assign AUD_SD = do_fwd | do_rev; // amp enable
assign AUD_PWM = (do_fwd | do_rev) ? noise_bit : 1'b0;
Thruster Flame Sprite
One new addition is a thruster flame that appears only when the up button is pressed. The flame is another 32×32 bitmap, rotated with the ship, and positioned just behind it depending on orientation.
The mask was generated with a simple Python tool that converts a PNG into a .mem
file. Alternatively, I also built a procedural tapered flame that looks nice without extra art assets.
sprite_bitmap32_rot #(
.MEMFILE("ship_flame_triangle_32x32.mem")
) u_flame (
.sx (fx),
.sy (fy),
.orient (orient),
.bit_on (flm_pix)
);
In the pixel mux, if the flame is active, it’s drawn in orange:
else if (flame_on && in_flm && flm_pix) begin
r_n = 4'hF; g_n = 4'h6; b_n = 4'h0; // orange
end
The result is that when you thrust forward, the ship now shows a flickering exhaust flame, making it feel much more alive.
Closing Thoughts
I’m really happy with how this project turned out:
- The sprite moves smoothly in the direction it’s facing.
- The stars make it feel like space.
- The thruster noise and flame tie it together.
It’s a great example of how FPGAs let me combine video, audio, and control logic in one neat package.
Next step might be adding missiles, enemies, or animating the flame further… but for now, I’m just enjoying flying my little ship around the stars.