mirror of
https://github.com/mist-devel/mist-board.git
synced 2026-01-23 10:38:31 +00:00
182 lines
5.5 KiB
Verilog
182 lines
5.5 KiB
Verilog
// A simple system-on-a-chip (SoC) for the MiST
|
|
// (c) 2015 Till Harbaum
|
|
|
|
// VGA controller generating 160x100 pixles. The VGA mode ised is 640x400
|
|
// combining every 4 row and column
|
|
|
|
// http://tinyvga.com/vga-timing/640x400@70Hz
|
|
|
|
module vga (
|
|
// reset and pixel clock
|
|
input reset,
|
|
input pclk,
|
|
|
|
// CPU interface (write only!)
|
|
input cpu_clk,
|
|
input cpu_wr,
|
|
input [13:0] cpu_addr,
|
|
input [7:0] cpu_data,
|
|
|
|
// enable/disable scanlines
|
|
input scanlines,
|
|
|
|
// output to VGA screen
|
|
output reg hs,
|
|
output reg vs,
|
|
output [5:0] r,
|
|
output [5:0] g,
|
|
output [5:0] b
|
|
);
|
|
|
|
// 640x400 70HZ VESA according to http://tinyvga.com/vga-timing/640x400@70Hz
|
|
parameter H = 640; // width of visible area
|
|
parameter HFP = 16; // unused time before hsync
|
|
parameter HS = 96; // width of hsync
|
|
parameter HBP = 48; // unused time after hsync
|
|
|
|
parameter V = 400; // height of visible area
|
|
parameter VFP = 12; // unused time before vsync
|
|
parameter VS = 2; // width of vsync
|
|
parameter VBP = 35; // unused time after vsync
|
|
|
|
reg[9:0] h_cnt; // horizontal pixel counter
|
|
reg[9:0] v_cnt; // vertical pixel counter
|
|
|
|
// both counters count from the begin of the visibla area
|
|
|
|
// horizontal pixel counter
|
|
always@(posedge pclk) begin
|
|
if(h_cnt==H+HFP+HS+HBP-1) h_cnt <= 10'd0;
|
|
else h_cnt <= h_cnt + 10'd1;
|
|
|
|
// generate negative hsync signal
|
|
if(h_cnt == H+HFP) hs <= 1'b0;
|
|
if(h_cnt == H+HFP+HS) hs <= 1'b1;
|
|
end
|
|
|
|
// veritical pixel counter
|
|
always@(posedge pclk) begin
|
|
// the vertical counter is processed at the begin of each hsync
|
|
if(h_cnt == H+HFP) begin
|
|
if(v_cnt==VS+VBP+V+VFP-1) v_cnt <= 10'd0;
|
|
else v_cnt <= v_cnt + 10'd1;
|
|
|
|
// generate positive vsync signal
|
|
if(v_cnt == V+VFP) vs <= 1'b1;
|
|
if(v_cnt == V+VFP+VS) vs <= 1'b0;
|
|
end
|
|
end
|
|
|
|
// 16000 bytes of internal video memory for 160x100 pixel at 8 Bit (RGB 332)
|
|
reg [7:0] vmem [160*100-1:0];
|
|
|
|
reg [13:0] video_counter;
|
|
reg [7:0] pixel;
|
|
reg dark_pix;
|
|
|
|
// hardware cursor (sprite)
|
|
reg [7:0] cursor_mask[8];
|
|
reg [7:0] cursor_data[8];
|
|
reg [7:0] cursor_col_0, cursor_col_1;
|
|
reg [3:0] cursor_hot_x, cursor_hot_y;
|
|
reg [7:0] cursor_x, cursor_y;
|
|
|
|
// write VRAM via CPU interface
|
|
always @(posedge cpu_clk) begin
|
|
if(reset)
|
|
cursor_x <= 8'd200; // cursor outside visible area
|
|
else begin
|
|
if(cpu_wr) begin
|
|
if(cpu_addr < 16000)
|
|
vmem[cpu_addr] <= cpu_data;
|
|
else if(cpu_addr == 14'h3efb)
|
|
cursor_col_0 <= cpu_data;
|
|
else if(cpu_addr == 14'h3efc)
|
|
cursor_col_1 <= cpu_data;
|
|
else if(cpu_addr == 14'h3efd) begin
|
|
cursor_hot_x <= cpu_data[7:4];
|
|
cursor_hot_y <= cpu_data[3:0];
|
|
end else if(cpu_addr == 14'h3efe)
|
|
cursor_x <= cpu_data;
|
|
else if(cpu_addr == 14'h3eff)
|
|
cursor_y <= cpu_data;
|
|
else if((cpu_addr >= 14'h3f00) && (cpu_addr < 14'h3f08))
|
|
cursor_data[cpu_addr[2:0]] <= cpu_data;
|
|
else if((cpu_addr >= 14'h3f08) && (cpu_addr < 14'h3f10))
|
|
cursor_mask[cpu_addr[2:0]] <= cpu_data;
|
|
end
|
|
end
|
|
end
|
|
|
|
// add hot spot info to cursor position
|
|
wire [7:0] cursor_map_x = cursor_x - { 4'h0, cursor_hot_x } /* synthesis keep */;
|
|
wire [7:0] cursor_map_y = cursor_y - { 4'h0, cursor_hot_y } /* synthesis keep */;
|
|
wire [7:0] cursor_data_byte = cursor_data[v_cnt[9:2] - cursor_map_y];
|
|
wire cursor_data_pix = cursor_data_byte[cursor_map_x - h_cnt[9:2] - 8'd1];
|
|
wire [7:0] cursor_mask_byte = cursor_mask[v_cnt[9:2] - cursor_map_y];
|
|
wire cursor_mask_pix = cursor_mask_byte[cursor_map_x - h_cnt[9:2] - 8'd1];
|
|
|
|
wire [7:0] vmem_byte = vmem[video_counter];
|
|
|
|
// read VRAM for video generation
|
|
always@(posedge pclk) begin
|
|
// The video counter is being reset at the begin of each vsync.
|
|
// Otherwise it's increased every fourth pixel in the visible area.
|
|
// At the end of the first three of four lines the counter is
|
|
// decreased by the total line length to display the same contents
|
|
// for four lines so 100 different lines are displayed on the 400
|
|
// VGA lines.
|
|
|
|
// visible area?
|
|
if((v_cnt < V) && (h_cnt < H)) begin
|
|
// increase video counter after each pixel
|
|
if(h_cnt[1:0] == 2'd3)
|
|
video_counter <= video_counter + 14'd1;
|
|
|
|
pixel <= vmem[video_counter]; // read VRAM
|
|
dark_pix <= 1'b0;
|
|
|
|
// draw cursor
|
|
if(((cursor_map_y+8'd8) > v_cnt[9:2]) &&
|
|
((cursor_map_y <= v_cnt[9:2]) || (cursor_map_y > 248)) &&
|
|
((cursor_map_x+8'd8) > h_cnt[9:2]) &&
|
|
((cursor_map_x <= h_cnt[9:2]) || (cursor_map_x > 248)) ) begin
|
|
|
|
if(cursor_mask_pix)
|
|
pixel <= cursor_data_pix?cursor_col_1:cursor_col_0;
|
|
else if(cursor_data_pix)
|
|
dark_pix <= 1'b1;
|
|
end
|
|
|
|
end else begin
|
|
// video counter is manipulated at the end of a line outside
|
|
// the visible area
|
|
if(h_cnt == H+HFP) begin
|
|
// the video counter is reset at the begin of the vsync
|
|
// at the end of three of four lines it's decremented
|
|
// one line to repeat the same pixels over four display
|
|
// lines
|
|
if(v_cnt == V+VFP)
|
|
video_counter <= 14'd0;
|
|
else if((v_cnt < V) && (v_cnt[1:0] != 2'd3))
|
|
video_counter <= video_counter - 14'd160;
|
|
end
|
|
|
|
pixel <= 8'h00; // color outside visible area: black
|
|
dark_pix <= 1'b0;
|
|
end
|
|
end
|
|
|
|
// split the 8 rgb bits into the three base colors. Every second line is
|
|
// darker when scanlines are enabled
|
|
wire dark = (scanlines && v_cnt[0]) || dark_pix;
|
|
wire [5:0] r_col = { pixel[7:5], pixel[7:5] };
|
|
wire [5:0] g_col = { pixel[4:2], pixel[4:2] };
|
|
wire [5:0] b_col = { pixel[1:0], pixel[1:0], pixel[1:0] };
|
|
|
|
assign r = (!dark)?{ r_col }:{ 1'b0, r_col[5:1] };
|
|
assign g = (!dark)?{ g_col }:{ 1'b0, g_col[5:1] };
|
|
assign b = (!dark)?{ b_col }:{ 1'b0, b_col[5:1] };
|
|
|
|
endmodule
|