- we still can only display 400 lines, so 80 blank lines are added at the bottom - we get square pixels this way and are hopefully more compatible with monitors and other devices like scan converters and capture cards
313 lines
12 KiB
Verilog
313 lines
12 KiB
Verilog
`timescale 1ns / 1ps
|
|
`default_nettype none
|
|
|
|
// Project F: Display Timings
|
|
// (C)2019 Will Green, Open Source Hardware released under the MIT License
|
|
// Learn more at https://projectf.io
|
|
|
|
//128K video memory is not enough for 640x480x4
|
|
//`define RES_640_400
|
|
//`define RES_1024_768
|
|
// RES_640_480 mode displays 400 lines with 640x480/60 video timings,
|
|
// adding blank lines at the bottom
|
|
`define RES_640_480
|
|
|
|
module display_timings #(
|
|
H_RES=640, // horizontal resolution (pixels)
|
|
V_RES=480, // vertical resolution (lines)
|
|
H_FP=16, // horizontal front porch
|
|
H_SYNC=96, // horizontal sync
|
|
H_BP=48, // horizontal back porch
|
|
V_FP=10, // vertical front porch
|
|
V_SYNC=2, // vertical sync
|
|
V_BP=33, // vertical back porch
|
|
H_POL=0, // horizontal sync polarity (0:neg, 1:pos)
|
|
V_POL=0 // vertical sync polarity (0:neg, 1:pos)
|
|
)
|
|
(
|
|
input wire i_pix_clk, // pixel clock
|
|
input wire i_rst, // reset: restarts frame (active high)
|
|
output wire o_hs, // horizontal sync
|
|
output wire o_vs, // vertical sync
|
|
output wire o_de, // display enable: high during active video
|
|
output wire o_frame, // high for one tick at the start of each frame
|
|
output wire o_scanline, // high for one tick at the start of each scanline
|
|
output reg o_vblank, // high during vertical blank phase
|
|
output reg signed [15:0] o_sx, // horizontal beam position (including blanking)
|
|
output reg signed [15:0] o_sy // vertical beam position (including blanking)
|
|
);
|
|
|
|
// Horizontal: sync, active, and pixels
|
|
localparam signed H_STA = 0 - H_FP - H_SYNC - H_BP; // horizontal start
|
|
localparam signed HS_STA = H_STA + H_FP; // sync start
|
|
localparam signed HS_END = HS_STA + H_SYNC; // sync end
|
|
localparam signed HA_STA = 0; // active start
|
|
localparam signed HA_END = H_RES - 1; // active end
|
|
|
|
// Vertical: sync, active, and pixels
|
|
localparam signed V_STA = 0 - V_FP - V_SYNC - V_BP; // vertical start
|
|
localparam signed VS_STA = V_STA + V_FP; // sync start
|
|
localparam signed VS_END = VS_STA + V_SYNC; // sync end
|
|
localparam signed VA_STA = 0; // active start
|
|
localparam signed VA_END = V_RES - 1; // active end
|
|
|
|
// generate sync signals with correct polarity
|
|
assign o_hs = H_POL ? (o_sx > HS_STA && o_sx <= HS_END)
|
|
: ~(o_sx > HS_STA && o_sx <= HS_END);
|
|
assign o_vs = V_POL ? (o_sy > VS_STA && o_sy <= VS_END)
|
|
: ~(o_sy > VS_STA && o_sy <= VS_END);
|
|
|
|
// display enable: high during active period
|
|
assign o_de = (o_sx >= 0 && o_sy >= 0);
|
|
|
|
// o_frame: high for one tick at the start of each frame
|
|
assign o_frame = (o_sy == V_STA && o_sx == H_STA);
|
|
// o_scanline: high for one tick at the start of each visible scanline
|
|
assign o_scanline = (o_sy >= VA_STA) && (o_sy <= VA_END) && (o_sx == H_STA);
|
|
|
|
// set vblank at end of frame, clear at start
|
|
always @(posedge i_pix_clk)
|
|
begin
|
|
if(o_sy == VA_END) o_vblank <= 1;
|
|
else if (o_sy == -1) o_vblank <= 0;
|
|
end
|
|
|
|
always @ (posedge i_pix_clk)
|
|
begin
|
|
if (i_rst) // reset to start of frame
|
|
begin
|
|
o_sx <= H_STA;
|
|
o_sy <= V_STA;
|
|
end
|
|
else
|
|
begin
|
|
if (o_sx == HA_END) // end of line
|
|
begin
|
|
o_sx <= H_STA;
|
|
if (o_sy == VA_END) // end of frame
|
|
o_sy <= V_STA;
|
|
else
|
|
o_sy <= o_sy + 16'sh1;
|
|
end
|
|
else
|
|
o_sx <= o_sx + 16'sh1;
|
|
end
|
|
end
|
|
endmodule
|
|
|
|
// Project F: Display Controller VGA Demo
|
|
// (C)2020 Will Green, Open source hardware released under the MIT License
|
|
// Learn more at https://projectf.io
|
|
|
|
// This demo requires the following Verilog modules:
|
|
// * display_clocks
|
|
// * display_timings
|
|
// * test_card_simple or another test card
|
|
|
|
module vgafb #(VMEM_ADDR_WIDTH = 15, VMEM_DATA_WIDTH = 32) (
|
|
input wire cpu_clk, // cpu clock
|
|
input wire CLK, // pixel clock
|
|
input wire RST_BTN, // reset button
|
|
input wire[3:0] reg_sel, // register select/address
|
|
output wire [VMEM_DATA_WIDTH-1:0] rd_data,
|
|
input wire [VMEM_DATA_WIDTH-1:0] wr_data,
|
|
input wire rd_en,
|
|
input wire wr_en,
|
|
|
|
output wire VGA_HS, // horizontal sync output
|
|
output wire VGA_VS, // vertical sync output
|
|
output wire [3:0] VGA_R, // 4-bit VGA red output
|
|
output wire [3:0] VGA_G, // 4-bit VGA green output
|
|
output wire [3:0] VGA_B // 4-bit VGA blue output
|
|
);
|
|
|
|
localparam BITS_PER_PIXEL = 4; localparam MAX_SHIFT_COUNT = 7;
|
|
localparam REG_RD_ADDR = 0; localparam REG_WR_ADDR = 1; localparam REG_VMEM = 2;
|
|
localparam REG_PAL_SLOT = 3; localparam REG_PAL_DATA = 4;
|
|
localparam REG_CTL = 5;
|
|
|
|
localparam COLOR_WIDTH = 12;
|
|
localparam PALETTE_WIDTH = 4;
|
|
|
|
localparam signed PIC_LINES = 400; // visible picture lines
|
|
|
|
// Display Clocks
|
|
wire pix_clk = CLK; // pixel clock
|
|
wire clk_lock = 1; // clock locked?
|
|
|
|
wire vmem_rd_en;
|
|
wire vmem_wr_en;
|
|
wire [VMEM_DATA_WIDTH-1:0] vmem_rd_data;
|
|
reg [VMEM_ADDR_WIDTH-1:0] cpu_rd_addr;
|
|
reg [VMEM_ADDR_WIDTH-1:0] cpu_wr_addr;
|
|
reg [VMEM_ADDR_WIDTH-1:0] pix_addr;
|
|
wire [VMEM_DATA_WIDTH-1:0] pix_data;
|
|
wire pix_rd;
|
|
wire [VMEM_DATA_WIDTH-1:0] status;
|
|
|
|
assign vmem_rd_en = rd_en;
|
|
assign vmem_wr_en = (reg_sel == REG_VMEM) && wr_en;
|
|
assign rd_data = (reg_sel == REG_VMEM) ? vmem_rd_data :
|
|
(reg_sel == REG_RD_ADDR) ? cpu_rd_addr :
|
|
(reg_sel == REG_WR_ADDR) ? cpu_wr_addr :
|
|
(reg_sel == REG_CTL) ? status :
|
|
32'hFFFFFFFF;
|
|
|
|
wire [VMEM_ADDR_WIDTH-1:0] cpu_addr = vmem_wr_en ? cpu_wr_addr : cpu_rd_addr;
|
|
|
|
bram_tdp #(.DATA(VMEM_DATA_WIDTH),.ADDR(VMEM_ADDR_WIDTH)) vram0 (
|
|
.a_rd(vmem_rd_en), .a_clk(cpu_clk),
|
|
.a_wr(vmem_wr_en), .a_addr(cpu_addr), .a_din(wr_data),
|
|
.a_dout(vmem_rd_data),
|
|
.b_clk(pix_clk), .b_addr(pix_addr), .b_dout(pix_data),
|
|
.b_rd(pix_rd)
|
|
);
|
|
|
|
wire palette_wr_en = (reg_sel == REG_PAL_DATA) && wr_en;
|
|
reg [PALETTE_WIDTH-1:0] palette_wr_slot;
|
|
wire [COLOR_WIDTH-1:0] color_data;
|
|
|
|
palette palette0(.wr_clk(cpu_clk), .rd_clk(pix_clk), .wr_en(palette_wr_en),
|
|
.wr_slot(palette_wr_slot), .wr_data(wr_data[COLOR_WIDTH-1:0]),
|
|
.rd_slot(pixel[3:0]), .rd_data(color_data));
|
|
|
|
// Display Timings
|
|
wire signed [15:0] sx; // horizontal screen position (signed)
|
|
wire signed [15:0] sy; // vertical screen position (signed)
|
|
wire h_sync; // horizontal sync
|
|
wire v_sync; // vertical sync
|
|
wire de; // display enable
|
|
wire frame; // frame start
|
|
wire scanline; // scanline start
|
|
wire vblank; // vertical blank
|
|
reg vblank_buf; // vertical blank in cpu clock domain
|
|
reg vblank_xfer; // vertical blank clock domain crossing
|
|
|
|
display_timings #( // 640x480 800x600 1280x720 1920x1080
|
|
`ifdef RES_1024_768
|
|
.H_RES(1024), // 640 800 1280 1920
|
|
.V_RES(768), // 480 600 720 1080
|
|
.H_FP(24), // 16 40 110 88
|
|
.H_SYNC(136), // 96 128 40 44
|
|
.H_BP(160), // 48 88 220 148
|
|
.V_FP(3), // 10 1 5 4
|
|
.V_SYNC(6), // 2 4 5 5
|
|
.V_BP(29), // 33 23 20 36
|
|
.H_POL(0), // 0 1 1 1
|
|
.V_POL(0) // 0 1 1 1
|
|
`endif
|
|
`ifdef RES_640_400
|
|
.H_RES(640),
|
|
.V_RES(400),
|
|
.H_FP(16),
|
|
.H_SYNC(96),
|
|
.H_BP(48),
|
|
.V_FP(12),
|
|
.V_SYNC(2),
|
|
.V_BP(35),
|
|
.H_POL(0),
|
|
.V_POL(1)
|
|
`endif
|
|
`ifdef RES_640_480
|
|
.H_RES(640), // 640 800 1280 1920
|
|
.V_RES(480), // 480 600 720 1080
|
|
.H_FP(16), // 16 40 110 88
|
|
.H_SYNC(96), // 96 128 40 44
|
|
.H_BP(48), // 48 88 220 148
|
|
.V_FP(10), // 10 1 5 4
|
|
.V_SYNC(2), // 2 4 5 5
|
|
.V_BP(33), // 33 23 20 36
|
|
.H_POL(0), // 0 1 1 1
|
|
.V_POL(0) // 0 1 1 1
|
|
`endif
|
|
)
|
|
display_timings_inst (
|
|
.i_pix_clk(CLK),
|
|
.i_rst(!RST_BTN),
|
|
.o_hs(h_sync),
|
|
.o_vs(v_sync),
|
|
.o_de(de),
|
|
.o_frame(frame),
|
|
.o_scanline(scanline),
|
|
.o_vblank(vblank),
|
|
.o_sx(sx),
|
|
.o_sy(sy)
|
|
);
|
|
|
|
wire pic_enable = (sy >= 0) && (sy < PIC_LINES); // when to display pixels from VRAM
|
|
|
|
wire [7:0] red;
|
|
wire [7:0] green;
|
|
wire [7:0] blue;
|
|
|
|
reg [VMEM_DATA_WIDTH-1:0] shifter;
|
|
reg [4:0] shift_count;
|
|
|
|
// delayed frame signal for pix_rd
|
|
reg frame_d;
|
|
|
|
assign pix_rd = frame_d || scanline || (shift_count == 2);
|
|
|
|
assign status = { 4'b0001, {(VMEM_DATA_WIDTH-5){1'b0}}, vblank_buf};
|
|
|
|
wire [BITS_PER_PIXEL-1:0] pixel = shifter[VMEM_DATA_WIDTH-1:VMEM_DATA_WIDTH-BITS_PER_PIXEL];
|
|
|
|
always @(posedge pix_clk) frame_d <= frame;
|
|
|
|
always @(posedge cpu_clk) { vblank_buf, vblank_xfer } <= { vblank_xfer, vblank };
|
|
|
|
always @(posedge cpu_clk)
|
|
begin
|
|
if(wr_en)
|
|
begin
|
|
case(reg_sel)
|
|
REG_RD_ADDR: cpu_rd_addr <= wr_data;
|
|
REG_WR_ADDR: cpu_wr_addr <= wr_data;
|
|
REG_VMEM: cpu_wr_addr <= cpu_wr_addr + 1; // auto-increment write addr on write
|
|
REG_PAL_SLOT: palette_wr_slot <= wr_data[3:0];
|
|
endcase
|
|
end
|
|
else
|
|
if(rd_en && reg_sel == REG_VMEM) cpu_rd_addr <= cpu_rd_addr + 1; // auto-increment read addr on read
|
|
end
|
|
|
|
always @(posedge pix_clk)
|
|
begin
|
|
if(scanline || shift_count == MAX_SHIFT_COUNT) // before start of a line
|
|
begin // or at the end of a word, reset shifter with pixel data
|
|
shift_count <= 0;
|
|
shifter <= pix_data;
|
|
end
|
|
else if(de)
|
|
begin
|
|
shift_count <= shift_count + 1;
|
|
shifter <= shifter << BITS_PER_PIXEL;
|
|
end
|
|
|
|
if(frame) // at start of frame, reset pixel pointer
|
|
pix_addr <= 0;
|
|
else if(shift_count == 1) // after the first pixel, increment address
|
|
pix_addr <= pix_addr + 1;
|
|
end
|
|
|
|
// Hard-Coded RGBI palette
|
|
// // Pixel = { red, green, blue, intensity }
|
|
// assign red = pixel[3] ? pixel[0] ? 255 : 127: 0;
|
|
// assign green = pixel[2] ? pixel[0] ? 255 : 127: 0;
|
|
// assign blue = pixel[1] ? pixel[0] ? 255 : 127: 0;
|
|
|
|
// // VGA Output
|
|
// // VGA Pmod is 12-bit so we take the upper nibble of each colour
|
|
// assign VGA_HS = h_sync;
|
|
// assign VGA_VS = v_sync;
|
|
// assign VGA_R = de ? red[7:4] : 4'b0;
|
|
// assign VGA_G = de ? green[7:4] : 4'b0;
|
|
// assign VGA_B = de ? blue[7:4] : 4'b0;
|
|
|
|
// 12 bit RGB palette
|
|
assign VGA_HS = h_sync;
|
|
assign VGA_VS = v_sync;
|
|
assign VGA_R = (pic_enable && de) ? color_data[11:8] : 4'b0;
|
|
assign VGA_G = (pic_enable && de) ? color_data[7:4] : 4'b0;
|
|
assign VGA_B = (pic_enable && de) ? color_data[3:0] : 4'b0;
|
|
endmodule
|