Rasterbars
This FPGA demo effect renders four animated rasterbars. I created this effect with benjamin.computer for All You Need, a Chapterhouse prod released at Revision 2022.
For an introduction to rasterbars and other simple graphics effects, check out Racing the Beam.
The design was originally for a custom Artix-7 dev board, but this version runs on the Digilent Arty A7 or as a Verilator/SDL simulation on your computer.
Building the Demo
Find the Verilog source and build instructions in the projf-explore git repo:
https://github.com/projf/projf-explore/tree/main/demos/rasterbars/
New to FPGA graphics design? Start with Beginning FPGA Graphics.
Demo Structure
- Top module with display interfaces
- Arty (XC7): xc7/top_rasterbars.sv
- Verilator/SDL: sim/top_rasterbars.sv
- Rasterbars render module: render_rasterbars.sv (see below)
- Rasterbar module: rasterbar.sv (see below)
- Project F library modules
I’ve written a separate post on the workings of the FPGA Sine Lookup Table.
Rasterbar Rendering
Explanation to follow.
Simple Rasterbar
module rasterbar (
input wire logic clk, // clock
input wire logic start, // start control
input wire logic line, // signal new screen line
input wire logic [11:0] base_colr, // base bar colour
input wire logic [3:0] colr_steps, // bar colour steps
input wire logic [3:0] colr_lines, // lines of each colour
output logic [11:0] bar_colr, // bar draw colour
output logic drawing, // bar is drawing
output logic done // bar drawing is complete
);
// NB. We don't (yet) register colr_steps and colr_lines
logic bar_inc; // increasing (or decreasing) brightness
logic [3:0] cnt_step; // count colour steps in each bar
logic [3:0] cnt_line; // count lines of each colour
// generate bar colours
always_ff @(posedge clk) begin
if (start) begin // reset colour at start of bar
bar_colr <= base_colr;
bar_inc <= 1; // start by increasing brightness
cnt_step <= 0;
cnt_line <= 0;
drawing <= 1;
done <= 0;
end else if (line) begin // on each screen line
if (cnt_line == colr_lines-1) begin // colour complete
cnt_line <= 0;
if (cnt_step == colr_steps-1) begin // switch increase/decrease
if (bar_inc == 1) begin
bar_inc <= 0;
cnt_step <= 0;
end else begin
drawing <= 0;
done <= 1;
bar_colr <= 12'h000;
end
end else begin
bar_colr <= (bar_inc) ? bar_colr + 12'h111 : bar_colr - 12'h111;
cnt_step <= cnt_step + 1;
end
end else cnt_line <= cnt_line + 1;
end
end
endmodule
Render Multiple Rasterbars
module render_rasterbars #(
parameter CORDW=16, // signed coordinate width
parameter VCENTER=180, // vertical centre of raster render
parameter COLR_LINES=3, // lines of each colour
parameter SIN_FILE="", // sine table ROM .mem file
parameter SIN_SHIFT=0 // right-shift sine values
) (
input wire logic clk, // clock
input wire logic start, // start control
input wire logic line, // line signal
input wire logic signed [CORDW-1:0] sy, // vertical position
output logic [11:0] bar_colr, // colour to draw
output logic bar_up // bar is drawing up
);
// sine table
localparam SIN_DEPTH=64; // entires in sine ROM 0°-90°
localparam SIN_WIDTH=8; // width of sine ROM data
localparam SIN_ADDRW=$clog2(4*SIN_DEPTH); // full table -180° to +180°
logic [SIN_ADDRW-1:0] sin_id, sin_offs;
logic signed [CORDW-1:0] sin_data; // sign extend data to match coords
sine_table #(
.ROM_DEPTH(SIN_DEPTH),
.ROM_WIDTH(SIN_WIDTH),
.ROM_FILE(SIN_FILE)
) sine_table_inst (
.id(sin_id + sin_offs),
.data(sin_data)
);
// raster A
localparam BASE_COLR_A = 12'h126; // bar start colour (blue)
localparam STEPS_A = 10; // colours steps in each bar
logic [11:0] bar_colr_a;
logic bar_drawing_a;
logic signed [CORDW-1:0] bar_y_a; // screen line to draw at
logic signed [CORDW-1:0] bar_y_a_prev; // previous line for direction detection
logic bar_up_a; // bar is moving up the screen?
rasterbar raster_a (
.clk,
.start(sy == bar_y_a),
.line,
.base_colr(BASE_COLR_A),
.colr_steps(STEPS_A),
.colr_lines(COLR_LINES),
.bar_colr(bar_colr_a),
.drawing(bar_drawing_a),
/* verilator lint_off PINCONNECTEMPTY */
.done()
/* verilator lint_on PINCONNECTEMPTY */
);
// raster B
localparam BASE_COLR_B = 12'h640; // bar start colour (gold)
localparam STEPS_B = 10; // colours steps in each bar
logic [11:0] bar_colr_b;
logic bar_drawing_b;
logic signed [CORDW-1:0] bar_y_b; // screen line to draw at
logic signed [CORDW-1:0] bar_y_b_prev; // previous line for direction detection
logic bar_up_b; // bar is moving up the screen?
rasterbar raster_b (
.clk,
.start(sy == bar_y_b),
.line,
.base_colr(BASE_COLR_B),
.colr_steps(STEPS_B),
.colr_lines(COLR_LINES),
.bar_colr(bar_colr_b),
.drawing(bar_drawing_b),
/* verilator lint_off PINCONNECTEMPTY */
.done()
/* verilator lint_on PINCONNECTEMPTY */
);
// raster C
localparam BASE_COLR_C = 12'h610; // bar start colour (red)
localparam STEPS_C = 10; // colours steps in each bar
logic [11:0] bar_colr_c;
logic bar_drawing_c;
logic signed [CORDW-1:0] bar_y_c; // screen line to draw at
logic signed [CORDW-1:0] bar_y_c_prev; // previous line for direction detection
logic bar_up_c; // bar is moving up the screen?
rasterbar raster_c (
.clk,
.start(sy == bar_y_c),
.line,
.base_colr(BASE_COLR_C),
.colr_steps(STEPS_C),
.colr_lines(COLR_LINES),
.bar_colr(bar_colr_c),
.drawing(bar_drawing_c),
/* verilator lint_off PINCONNECTEMPTY */
.done()
/* verilator lint_on PINCONNECTEMPTY */
);
// raster D
localparam BASE_COLR_D = 12'h145; // bar start colour (greenish)
localparam STEPS_D = 10; // colours steps in each bar
logic [11:0] bar_colr_d;
logic bar_drawing_d;
logic signed [CORDW-1:0] bar_y_d; // screen line to draw at
logic signed [CORDW-1:0] bar_y_d_prev; // previous line for direction detection
logic bar_up_d; // bar is moving up the screen?
rasterbar raster_d (
.clk,
.start(sy == bar_y_d),
.line,
.base_colr(BASE_COLR_D),
.colr_steps(STEPS_D),
.colr_lines(COLR_LINES),
.bar_colr(bar_colr_d),
.drawing(bar_drawing_d),
/* verilator lint_off PINCONNECTEMPTY */
.done()
/* verilator lint_on PINCONNECTEMPTY */
);
// update bar positions with sine table
enum {INIT, BAR_A, BAR_B, BAR_C, BAR_D, DONE} state;
always_ff @(posedge clk) begin
case (state)
INIT: begin
state <= BAR_A;
sin_id <= sin_id + 1;
sin_offs <= 0;
end
BAR_A: begin
state <= BAR_B;
bar_y_a <= VCENTER + (sin_data >>> SIN_SHIFT);
bar_y_a_prev <= bar_y_a;
sin_offs <= 64; // offset for bar B: 64/256
end
BAR_B: begin
state <= BAR_C;
bar_y_b <= VCENTER + (sin_data >>> SIN_SHIFT);
bar_y_b_prev <= bar_y_b;
sin_offs <= 128; // offset for bar C: 128/256
end
BAR_C: begin
state <= BAR_D;
bar_y_c <= VCENTER + (sin_data >>> SIN_SHIFT);
bar_y_c_prev <= bar_y_c;
sin_offs <= 192; // offset for bar D: 192/256
end
BAR_D: begin
state <= DONE;
bar_y_d <= VCENTER + (sin_data >>> SIN_SHIFT);
bar_y_d_prev <= bar_y_d;
end
default: if (start) state <= INIT;
endcase
end
// bar rising or falling?
always_comb begin
bar_up_a = (bar_y_a < bar_y_a_prev);
bar_up_b = (bar_y_b < bar_y_b_prev);
bar_up_c = (bar_y_c < bar_y_c_prev);
bar_up_d = (bar_y_d < bar_y_d_prev);
end
always_ff @(posedge clk) begin
if (bar_drawing_a && bar_up_a) bar_colr <= bar_colr_a;
else if (bar_drawing_b && bar_up_b) bar_colr <= bar_colr_b;
else if (bar_drawing_c && bar_up_c) bar_colr <= bar_colr_c;
else if (bar_drawing_d && bar_up_d) bar_colr <= bar_colr_d;
else if (bar_drawing_a) bar_colr <= bar_colr_a;
else if (bar_drawing_b) bar_colr <= bar_colr_b;
else if (bar_drawing_c) bar_colr <= bar_colr_c;
else if (bar_drawing_d) bar_colr <= bar_colr_d;
else bar_colr <= 12'h000;
end
always_ff @(posedge clk) begin
if ((bar_drawing_a && bar_up_a) || (bar_drawing_b && bar_up_b) ||
(bar_drawing_c && bar_up_c) || (bar_drawing_d && bar_up_d))
bar_up <= 1;
else bar_up <= 0;
end
endmodule
This is a quick design for a demo, not an example of good hardware design practice! For one thing, I’m not proud of that huge if-else statement to determine bar drawing order!
What’s Next?
Check out my demos, FPGA graphics tutorials, and guide to FPGA sine lookup tables.
Get in touch on Mastodon, Bluesky, or X. If you enjoy my work, please sponsor me. 🙏