Skip to content

Instantly share code, notes, and snippets.

@urish
Created May 21, 2024 11:12
Show Gist options
  • Save urish/421920c1361b1d3dd132630b4ee84063 to your computer and use it in GitHub Desktop.
Save urish/421920c1361b1d3dd132630b4ee84063 to your computer and use it in GitHub Desktop.
Matt's VGA Clock for the VGA Clock Playground
/*
* Copyright (c) 2024 Uri Shaked
* SPDX-License-Identifier: Apache-2.0
*/
`default_nettype none
module tt_um_vga_example(
input wire [7:0] ui_in, // Dedicated inputs
output wire [7:0] uo_out, // Dedicated outputs
input wire [7:0] uio_in, // IOs: Input path
output wire [7:0] uio_out, // IOs: Output path
output wire [7:0] uio_oe, // IOs: Enable path (active high: 0=input, 1=output)
input wire ena, // always 1 when the design is powered, so you can ignore it
input wire clk, // clock
input wire rst_n // reset_n - low to reset
);
assign uio_out = 8'b0;
assign uio_oe = 8'b0;
wire [1:0] R;
wire [1:0] G;
wire [1:0] B;
wire hsync, vsync;
wire vga_clock_pmod = ui_in[3];
// this input switches between Tiny VGA and VGA Clock PMOD
// https://github.com/mole99/tiny-vga
// https://github.com/TinyTapeout/tt-vga-clock-pmod
assign uo_out[0] = vga_clock_pmod ? hsync : R[1];
assign uo_out[1] = vga_clock_pmod ? vsync : G[1];
assign uo_out[2] = vga_clock_pmod ? B[0] : B[1];
assign uo_out[3] = vga_clock_pmod ? B[1] : vsync;
assign uo_out[4] = vga_clock_pmod ? G[0] : R[0];
assign uo_out[5] = vga_clock_pmod ? G[1] : G[0];
assign uo_out[6] = vga_clock_pmod ? R[0] : B[0];
assign uo_out[7] = vga_clock_pmod ? R[1] : hsync;
vga_clock vga_clock (
.clk (clk),
.reset_n (rst_n),
// inputs
.adj_hrs (ui_in[0]),
.adj_min (ui_in[1]),
.adj_sec (ui_in[2]),
// outputs
.hsync (hsync),
.vsync (vsync),
.rrggbb ({R,G,B})
);
endmodule
module vga_clock (
input wire clk,
input wire reset_n,
input wire adj_hrs,
input wire adj_min,
input wire adj_sec,
output wire hsync,
output wire vsync,
output wire [5:0] rrggbb
);
wire reset = !reset_n;
reg [3:0] sec_u;
reg [2:0] sec_d;
reg [3:0] min_u;
reg [2:0] min_d;
reg [3:0] hrs_u;
reg [1:0] hrs_d;
reg [25:0] sec_counter;
always @(posedge px_clk) begin
if(reset) begin
sec_u <= 0;
sec_d <= 0;
min_u <= 0;
min_d <= 0;
hrs_u <= 0;
hrs_d <= 0;
sec_counter <= 0;
color_offset <= 0;
end else begin
if(sec_u == 10) begin
sec_u <= 0;
sec_d <= sec_d + 1;
end
if(sec_d == 6) begin
sec_d <= 0;
min_u <= min_u + 1;
color_offset <= color_offset + 1;
end
if(min_u == 10) begin
min_u <= 0;
min_d <= min_d + 1;
end
if(min_d == 6) begin
min_d <= 0;
hrs_u <= hrs_u + 1;
end
if(hrs_u == 10) begin
hrs_u <= 0;
hrs_d <= hrs_d + 1;
end
if(hrs_d == 2 && hrs_u == 4) begin
hrs_u <= 0;
hrs_d <= 0;
end
// second counter
sec_counter <= sec_counter + 1;
if(sec_counter + 1 == 31_500_000) begin
sec_u <= sec_u + 1;
sec_counter <= 0;
end
// adjustment buttons
if(adj_sec_pulse)
sec_u <= sec_u + 1;
if(adj_min_pulse) begin
min_u <= min_u + 1;
color_offset <= color_offset + 1;
end
if(adj_hrs_pulse)
hrs_u <= hrs_u + 1;
end
end
wire adj_sec_pulse, adj_min_pulse, adj_hrs_pulse;
// want button_clk_en to be about 10ms
// frame rate is 70hz is 15ms
wire but_clk_en = y_px == 0 && x_px == 0;
localparam MAX_BUT_RATE = 16;
localparam DEC_COUNT = 1;
localparam MIN_COUNT = 2;
button_pulse #(.MIN_COUNT(MIN_COUNT), .DEC_COUNT(DEC_COUNT), .MAX_COUNT(MAX_BUT_RATE))
pulse_sec (.clk(px_clk), .clk_en(but_clk_en), .button(adj_sec), .pulse(adj_sec_pulse), .reset(reset));
button_pulse #(.MIN_COUNT(MIN_COUNT), .DEC_COUNT(DEC_COUNT), .MAX_COUNT(MAX_BUT_RATE))
pulse_min (.clk(px_clk), .clk_en(but_clk_en), .button(adj_min), .pulse(adj_min_pulse), .reset(reset));
button_pulse #(.MIN_COUNT(MIN_COUNT), .DEC_COUNT(DEC_COUNT), .MAX_COUNT(MAX_BUT_RATE))
pulse_hrs (.clk(px_clk), .clk_en(but_clk_en), .button(adj_hrs), .pulse(adj_hrs_pulse), .reset(reset));
// these are in blocks
localparam OFFSET_Y_BLK = 0;
localparam OFFSET_X_BLK = 1;
localparam NUM_CHARS = 8;
localparam FONT_W = 4;
localparam FONT_H = 5;
localparam COLON = 10;
localparam BLANK = 11;
localparam COL_INDEX_W = $clog2(FONT_W);
wire [9:0] x_px; // X position for actual pixel.
wire [9:0] y_px; // Y position for actual pixel.
// blocks are 16 x 16 px. total width = 8 * blocks of 4 = 512.
/* verilator lint_off WIDTH */
wire [5:0] x_block = (x_px -64) >> 4;
wire [5:0] y_block = (y_px -200) >> 4;
/* verilator lint_on WIDTH */
reg [5:0] x_block_q;
reg [5:0] y_block_q;
// reg [5:0] x_block = 0;
// reg [5:0] y_block = 0;
wire activevideo;
wire px_clk;
assign px_clk = clk;
hvsync_generator hvsync_gen(
.clk(px_clk),
.reset(~reset_n),
.hsync(hsync),
.vsync(vsync),
.display_on(activevideo),
.hpos(x_px),
.vpos(y_px)
);
wire [FONT_W-1:0] font_out;
wire [5:0] font_addr;
fontROM #(.data_width(FONT_W)) font_0 (.clk(px_clk), .addr(font_addr), .dout(font_out));
wire [5:0] digit_index;
wire [5:0] color;
reg [3:0] color_offset;
wire [3:0] number;
wire [COL_INDEX_W-1:0] col_index;
reg [COL_INDEX_W-1:0] col_index_q;
initial begin
$display(FONT_W);
$display(COL_INDEX_W);
end
digit #(.FONT_W(FONT_W), .FONT_H(FONT_H), .NUM_BLOCKS(NUM_CHARS*FONT_W)) digit_0 (.clk(px_clk), .x_block(x_block), .number(number), .digit_index(digit_index), .col_index(col_index), .color(color), .color_offset(color_offset));
/* verilator lint_off WIDTH */
assign number = x_block < FONT_W * 1 ? hrs_d :
x_block < FONT_W * 2 ? hrs_u :
x_block < FONT_W * 3 ? COLON :
x_block < FONT_W * 4 ? min_d :
x_block < FONT_W * 5 ? min_u :
x_block < FONT_W * 6 ? COLON :
x_block < FONT_W * 7 ? sec_d :
x_block < FONT_W * 8 ? sec_u :
BLANK;
/* verilator lint_on WIDTH */
assign rrggbb = activevideo && draw ? color : 6'b0;
assign font_addr = digit_index + y_block;
reg draw;
always @(posedge px_clk) begin
if(reset)
draw <= 0;
x_block_q <= x_block;
y_block_q <= y_block;
col_index_q <= col_index;
if(x_block_q < FONT_W * NUM_CHARS && y_block_q < FONT_H)
draw <= font_out[(FONT_W - 1) - col_index_q];
else
draw <= 0;
end
endmodule
module button_pulse
#(
parameter MAX_COUNT = 8, // max wait before issue next pulse
parameter DEC_COUNT = 2, // every pulse, decrement comparitor by this amount
parameter MIN_COUNT = 1 // until reaches this wait time
)(
input wire clk,
input wire clk_en,
input wire button,
input wire reset,
output wire pulse
);
reg [$clog2(MAX_COUNT-1):0] comp;
reg [$clog2(MAX_COUNT-1):0] count;
assign pulse = (clk_en && button && count == 0);
always @(posedge clk)
if(reset) begin
comp <= MAX_COUNT - 1;
count <= 0;
end else
if(clk_en) begin
if(button)
count <= count + 1;
// if button is held, increase pulse rate by reducing comp
if(count == 0 && comp > (MIN_COUNT + DEC_COUNT)) begin
comp <= comp - DEC_COUNT;
end
// reset counter
if(count == comp)
count <= 0;
// if button is released, set count and comp to default
if(!button) begin
count <= 0;
comp <= MAX_COUNT - 1;
end
end
/*
`ifdef FORMAL
default clocking @(posedge clk); endclocking
default disable iff (!clk_en);
cover property (##1 $rose(pulse));
cover property (comp < MAX_COUNT - 1);
assert property (button |=> comp <= $past(comp));
assert property (button && count != comp |=> count >= $past(count));
assert property (button && count == 0 |-> pulse);
assert property (pulse |=> !pulse);
`endif
*/
endmodule
`default_nettype none
module digit
#(
parameter DIGIT_INDEX_FILE = "digit_index.hex",
parameter COL_INDEX_FILE = "col_index.hex",
parameter COLOR_INDEX_FILE = "color.hex",
parameter FONT_W = 3,
parameter FONT_H = 5,
parameter NUM_BLOCKS = 20
)
(
input wire clk,
input wire [5:0] x_block,
// input wire [5:0] y_block,
input wire [3:0] number, // the number to display: [0->9: ]
input wire [3:0] color_offset, // shift through the colours
output reg [5:0] digit_index,
output reg [5:0] color,
output reg [COL_INDEX_W-1:0] col_index
);
localparam COL_INDEX_W = $clog2(FONT_W);
reg [5:0] digit_index_mem [0:11];
reg [COL_INDEX_W-1:0] col_index_mem [0:NUM_BLOCKS];
reg [5:0] color_index_mem [0:7];
initial begin
digit_index_mem[0] = 8'h00;
digit_index_mem[1] = 8'h05;
digit_index_mem[2] = 8'h0a;
digit_index_mem[3] = 8'h0f;
digit_index_mem[4] = 8'h14;
digit_index_mem[5] = 8'h19;
digit_index_mem[6] = 8'h1e;
digit_index_mem[7] = 8'h23;
digit_index_mem[8] = 8'h28;
digit_index_mem[9] = 8'h2d;
digit_index_mem[10] = 8'h32;
digit_index_mem[11] = 8'h37;
col_index_mem[0] = 8'h00;
col_index_mem[1] = 8'h01;
col_index_mem[2] = 8'h02;
col_index_mem[3] = 8'h03;
col_index_mem[4] = 8'h00;
col_index_mem[5] = 8'h01;
col_index_mem[6] = 8'h02;
col_index_mem[7] = 8'h03;
col_index_mem[8] = 8'h00;
col_index_mem[9] = 8'h01;
col_index_mem[10] = 8'h02;
col_index_mem[11] = 8'h03;
col_index_mem[12] = 8'h00;
col_index_mem[13] = 8'h01;
col_index_mem[14] = 8'h02;
col_index_mem[15] = 8'h03;
col_index_mem[16] = 8'h00;
col_index_mem[17] = 8'h01;
col_index_mem[18] = 8'h02;
col_index_mem[19] = 8'h03;
col_index_mem[20] = 8'h00;
col_index_mem[21] = 8'h01;
col_index_mem[22] = 8'h02;
col_index_mem[23] = 8'h03;
col_index_mem[24] = 8'h00;
col_index_mem[25] = 8'h01;
col_index_mem[26] = 8'h02;
col_index_mem[27] = 8'h03;
col_index_mem[28] = 8'h00;
col_index_mem[29] = 8'h01;
col_index_mem[30] = 8'h02;
col_index_mem[31] = 8'h03;
color_index_mem[0] = 6'b110000;
color_index_mem[1] = 6'b111000;
color_index_mem[2] = 6'b111100;
color_index_mem[3] = 6'b001000;
color_index_mem[4] = 6'b000011;
color_index_mem[5] = 6'b100010;
color_index_mem[6] = 6'b010010;
color_index_mem[7] = 6'b110000;
end
wire [3:0] char = x_block[5:2];
always @(posedge clk) begin
digit_index <= digit_index_mem[number];
col_index <= col_index_mem[x_block < NUM_BLOCKS ? x_block : NUM_BLOCKS-1];
color <= color_index_mem[char + color_offset];
end
endmodule
//////////////////////////////////////////////////////////////////////////////////
// Company: Ridotech
// Engineer: Juan Manuel Rico
//
// Create Date: 21:30:38 26/04/2018
// Module Name: fontROM
//
// Description: Font ROM for numbers (16x19 bits for numbers 0 to 9).
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
//
// Additional Comments:
//
//-----------------------------------------------------------------------------
//-- GPL license
//-----------------------------------------------------------------------------
module fontROM
#(
parameter FONT_FILE = "font.list",
parameter addr_width = 6,
parameter data_width = 4
)
(
input wire clk,
input wire [addr_width-1:0] addr,
output reg [data_width-1:0] dout
);
reg [data_width-1:0] mem [(1 << addr_width)-1:0];
initial begin
mem[0] = 4'b1110;
mem[1] = 4'b1010;
mem[2] = 4'b1010;
mem[3] = 4'b1010;
mem[4] = 4'b1110;
mem[5] = 4'b1100;
mem[6] = 4'b0100;
mem[7] = 4'b0100;
mem[8] = 4'b0100;
mem[9] = 4'b1110;
mem[10] = 4'b1110;
mem[11] = 4'b0010;
mem[12] = 4'b1110;
mem[13] = 4'b1000;
mem[14] = 4'b1110;
mem[15] = 4'b1110;
mem[16] = 4'b0010;
mem[17] = 4'b1110;
mem[18] = 4'b0010;
mem[19] = 4'b1110;
mem[20] = 4'b1010;
mem[21] = 4'b1010;
mem[22] = 4'b1110;
mem[23] = 4'b0010;
mem[24] = 4'b0010;
mem[25] = 4'b1110;
mem[26] = 4'b1000;
mem[27] = 4'b1110;
mem[28] = 4'b0010;
mem[29] = 4'b1110;
mem[30] = 4'b1000;
mem[31] = 4'b1000;
mem[32] = 4'b1110;
mem[33] = 4'b1010;
mem[34] = 4'b1110;
mem[35] = 4'b1110;
mem[36] = 4'b0010;
mem[37] = 4'b0100;
mem[38] = 4'b1000;
mem[39] = 4'b1000;
mem[40] = 4'b1110;
mem[41] = 4'b1010;
mem[42] = 4'b1110;
mem[43] = 4'b1010;
mem[44] = 4'b1110;
mem[45] = 4'b1110;
mem[46] = 4'b1010;
mem[47] = 4'b1110;
mem[48] = 4'b0010;
mem[49] = 4'b0010;
mem[50] = 4'b0000;
mem[51] = 4'b0100;
mem[52] = 4'b0000;
mem[53] = 4'b0100;
mem[54] = 4'b0000;
mem[55] = 4'b0000;
mem[56] = 4'b0000;
mem[57] = 4'b0000;
mem[58] = 4'b0000;
mem[59] = 4'b0000;
end
always @(posedge clk)
begin
dout <= mem[addr];
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment