1
0
mirror of https://github.com/Gehstock/Mist_FPGA.git synced 2026-05-06 00:04:10 +00:00
Files
Gehstock.Mist_FPGA/common/Video/tms9918a_bartlett/vdp_fsm.v
2019-07-22 23:42:05 +02:00

634 lines
17 KiB
Verilog

module vdp_fsm(
clk40m,
rst_n,
g1_mode,
g2_mode,
multi_mode,
gmode,
text_mode,
spr_size,
spr_mag,
blank_n,
ntb,
colb,
pgb,
sab,
spgb,
hsync,
vsync,
start_vblank,
set_mode,
visible,
border,
vram_req,
vram_wr,
vram_ack,
vram_addr,
vram_rdata,
vram_cpu_req,
vram_cpu_wr,
vram_cpu_ack,
vram_cpu_a,
pattern,
color,
load,
spr_pattern,
spr_color,
spr_collide,
spr_5,
spr_5num,
spr_nolimit
);
input clk40m;
input rst_n;
input g1_mode;
input g2_mode;
input multi_mode;
input gmode;
input text_mode;
input spr_size;
input spr_mag;
input blank_n;
input [ 3 : 0 ] ntb;
input [ 7 : 0 ] colb;
input [ 2 : 0 ] pgb;
input [ 6 : 0 ] sab;
input [ 2 : 0 ] spgb;
output hsync;
output vsync;
output start_vblank;
output set_mode;
output visible;
output border;
output vram_req;
output vram_wr;
input vram_ack;
output [ 13 : 0 ] vram_addr;
input [ 7 : 0 ] vram_rdata;
input vram_cpu_req;
input vram_cpu_wr;
output vram_cpu_ack;
input [ 13 : 0 ] vram_cpu_a;
output [ 7 : 0 ] pattern;
output [ 7 : 0 ] color;
output load;
output spr_pattern;
output [ 3 : 0 ] spr_color;
output spr_collide;
output spr_5;
output [ 4 : 0 ] spr_5num;
input spr_nolimit;
// 800x600 @ 60 Hz SVGA, pixel frequency = 40 MHz, +HSYNC, +VSYNC.
// H: 800 active, 40 front porch, 128 sync, 88 back porch = 1056 pixel clocks per line.
// V: 600 active, 1 front porch, 4 sync, 23 back porch = 628 lines.
// TMS9918A pixels are 3x3 SVGA pixels each (768x576 visible non-border).
`define H_ACT 800
`define H_FP 40
`define H_SYNC 128
`define H_BP 88
`define H_WIDTH `H_ACT+`H_FP+`H_SYNC+`H_BP
`define V_ACT 600
`define V_FP 1
`define V_SYNC 4
`define V_BP 23
`define V_HEIGHT `V_ACT+`V_FP+`V_SYNC+`V_BP
reg [ 10 : 0 ] h;
reg [ 9 : 0 ] v;
reg hsync;
reg vsync;
reg start_vblank;
reg line_end;
reg set_mode;
reg visible;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
h <= 0;
v <= 0;
hsync <= 0;
vsync <= 0;
start_vblank <= 0;
line_end <= 0;
set_mode <= 0;
visible <= 1;
end else begin
hsync <= ( h >= `H_ACT+`H_FP-1 && h < `H_ACT+`H_FP+`H_SYNC-1 );
line_end <= ( h == `H_WIDTH-2 );
set_mode <= ( h == `H_WIDTH-3 ) && ( v == `V_HEIGHT-1 );
start_vblank <= ( h == `H_WIDTH-2 ) && ( v == `V_ACT-1 );
visible <= ( h >= `H_WIDTH-2 && ( v == `V_HEIGHT-1 || v < `V_ACT-1 ) )
|| ( h < `H_ACT-2 && v <= `V_ACT-1 );
if( line_end ) begin
h <= 0;
if( v >= `V_ACT+`V_FP-1 && v < `V_ACT+`V_FP+`V_SYNC-1 ) begin
vsync <= 1;
end else begin
vsync <= 0;
end
if( v == `V_HEIGHT-1 ) begin
v <= 0;
end else begin
v <= v + 1'b1;
end
end else begin
h <= h + 1'b1;
end
end
end
`define BORD_TOP 12
`define BORD_BOT 12
reg [ 1 : 0 ] vrep; // Vertical line repeat x3.
reg [ 7 : 0 ] scan_line; // Active scan line, 0-191+.
reg [ 7 : 0 ] scan_next; // Scan line + 1.
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
vrep <= 0;
scan_line <= 192;
scan_next <= 193;
end else if( line_end ) begin
if( v == `BORD_TOP-4 ) begin
// For sprite setup vrep must be valid for 3 scan lines before active region.
vrep <= 0;
scan_line <= 8'hFF; // Bit 3 must be odd for sprites.
scan_next <= 0;
end else if( vrep == 2 ) begin
vrep <= 0;
scan_line <= scan_next;
scan_next <= scan_next + 1'b1;
end else begin
vrep <= vrep + 1'b1;
end
end
end
wire [ 2 : 0 ] line = scan_line[ 2 : 0 ]; // Scan line within pattern, 0-7.
wire [ 5 : 0 ] multi_row = scan_line[ 7 : 2 ]; // Multicolor block row, 0-47.
wire [ 4 : 0 ] row = scan_line[ 7 : 3 ]; // Pattern row, 0-23.
// Border delineation.
// Top and bottom borders are 12 SVGA lines each.
// In text mode, left and right borders are 40 SVGA pixels each.
// In graphics modes, left and right borders are 16 SVGA pixels each.
`define BORD_GRAPH 16
`define BORD_TEXT 40
reg border;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
border <= 1;
end else if( v < `BORD_TOP || v >= `V_ACT-`BORD_BOT ) begin
border <= 1;
end else if( blank_n && text_mode ) begin
border <= !( h >= `BORD_TEXT-2 && h < `H_ACT-`BORD_TEXT-2 );
end else if( blank_n && gmode ) begin
border <= !( h >= `BORD_GRAPH-2 && h < `H_ACT-`BORD_GRAPH-2 );
end else begin
border <= 1;
end
end
// Horizontal pattern counter, all modes.
wire ghnext, thnext;
reg [ 5 : 0 ] col;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
col <= 0;
end else if( line_end ) begin
col <= 0;
end else if( ghnext || thnext ) begin
col <= col + 1'b1;
end
end
// Graphics modes horizontal counter.
reg [ 4 : 0 ] ghcount;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
ghcount <= 0;
end else if( line_end || ghnext || !gmode ) begin
ghcount <= 0;
end else begin
ghcount <= ghcount + 1'b1;
end
end
assign ghnext = ( ghcount == 23 );
wire ghload = ( ghcount == 14 );
// Text mode horizontal counter.
reg [ 4 : 0 ] thcount;
reg [ 9 : 0 ] text_pos; // 40*pattern row + column.
reg [ 9 : 0 ] save_pos;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
thcount <= 0;
text_pos <= 0;
save_pos <= 0;
end else if( !text_mode ) begin
thcount <= 0;
end else if( line_end ) begin
thcount <= 0;
if( v == `BORD_TOP-1 ) begin
text_pos <= 0;
save_pos <= 0;
end else if( vrep != 2 || line != 7 ) begin
// For 23 additional lines in text row, restore text position.
text_pos <= save_pos;
end else begin
save_pos <= text_pos;
end
end else if( thnext ) begin
thcount <= 0;
text_pos <= text_pos + 1'b1;
end else if( thcount == 3 && ( h < 24 || h >= 744 || v < `BORD_TOP || v >= `V_ACT-`BORD_BOT ) ) begin
// Allow more CPU accesses during non-active time.
thcount <= 0;
end else begin
thcount <= thcount + 1'b1;
end
end
assign thnext = ( thcount == 17 );
wire thload = ( thcount == 14 );
// Load pattern into pixel shifter.
assign load = ( ghload || thload );
reg [ 4 : 0 ] spr_num;
always @( vrep or col ) begin
spr_num = 5'hXX;
if( vrep == 0 ) begin
spr_num = { 1'b0, col[ 4 : 1 ] };
end else if( vrep == 1 ) begin
spr_num = { 1'b1, col[ 4 : 1 ] };
end else if( vrep == 2 ) begin
spr_num = col[ 4 : 0 ];
end
end
reg [ 31 : 0 ] spr_xvld;
reg spr_load;
// VRAM requests. Must be mutually exclusive and at least 4 clocks apart.
wire pre_gnreq = ( ghcount == 0 ) && ( col < 32 ) && gmode;
wire pre_gpreq = ( ghcount == 4 ) && ( col < 32 ) && gmode;
wire pre_gcreq = ( ghcount == 8 ) && ( col < 32 ) && ( g1_mode || g2_mode );
wire pre_gxreq = ( ghcount == 20 ) && gmode;
// Sprite data is read from VRAM during the three SVGA lines prior to the corresponding three VDP lines.
// Sprite 0 is read on v = 9,10,11 for the first active VDP line at v = 12,13,14.
// Sprite 31 is read on v = 582,583,584 for the last active VDP line at v = 585,586,587.
wire pre_svreq = ( ghcount == 12 ) && !col[ 0 ] && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && ( vrep == 0 || vrep == 1 )
&& spr_load && blank_n && gmode;
wire pre_snreq = ( ghcount == 16 ) && !col[ 0 ] && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && ( vrep == 0 || vrep == 1 )
&& spr_load && blank_n && gmode;
wire pre_sp0req = ( ghcount == 12 ) && col[ 0 ] && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && ( vrep == 0 || vrep == 1 )
&& spr_xvld[ spr_num ] && blank_n && gmode;
wire pre_sp1req = ( ghcount == 16 ) && col[ 0 ] && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && ( vrep == 0 || vrep == 1 )
&& spr_xvld[ spr_num ] && blank_n && gmode && spr_size;
wire pre_sareq = ( ghcount == 12 ) && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && vrep == 2
&& spr_xvld[ spr_num ] && blank_n && gmode;
wire pre_shreq = ( ghcount == 16 ) && ( col < 32 )
&& ( v > 8 ) && ( v < 585 ) && vrep == 2
&& spr_xvld[ spr_num ] && blank_n && gmode;
wire pre_tnreq = ( thcount == 4 ) && text_mode;
wire pre_tpreq = ( thcount == 8 ) && text_mode;
wire pre_txreq = ( thcount == 0 || thcount == 12 ) && text_mode;
reg nreq, preq, creq, ureq, xureq;
reg svreq, shreq, snreq, sareq, sp0req, sp1req;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
nreq <= 0;
preq <= 0;
creq <= 0;
ureq <= 0;
svreq <= 0;
shreq <= 0;
snreq <= 0;
sareq <= 0;
sp0req <= 0;
end else begin
nreq <= blank_n && ( scan_line < 192 ) && ( pre_gnreq || pre_tnreq );
preq <= blank_n && ( scan_line < 192 ) && ( pre_gpreq || pre_tpreq );
creq <= blank_n && ( scan_line < 192 ) && pre_gcreq;
ureq <= ( v >= `BORD_TOP-4 && v < `V_ACT-`BORD_BOT ) && blank_n && ( pre_gxreq || pre_txreq );
xureq <= ( h[ 1 : 0 ] == 2'b00 ) && ( !blank_n
|| ( !gmode && !text_mode )
|| ( v < `BORD_TOP-4 || v >= `V_ACT-`BORD_BOT ) );
svreq <= pre_svreq;
shreq <= pre_shreq;
snreq <= pre_snreq;
sareq <= pre_sareq;
sp0req <= pre_sp0req;
sp1req <= pre_sp1req;
end
end
wire xreq = vram_cpu_req && ( ureq || xureq ) && !vram_cpu_ack;
wire vram_req = ( nreq || preq || creq || svreq || shreq || snreq || sareq || sp0req || sp1req || xreq );
wire vram_wr = ( xreq && vram_cpu_wr );
// With the 3-state SRAM controller, ACK is asserted one clock before valid read data.
reg vram_ack2;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
vram_ack2 <= 0;
end else begin
vram_ack2 <= vram_ack;
end
end
reg [ 9 : 0 ] reqs;
always @( posedge clk40m ) begin
if( vram_req ) begin
// Store each req so we know what to do with the ack.
reqs <= { svreq, shreq, snreq, sareq, sp0req, sp1req, nreq, preq, creq, xreq };
end else if( vram_ack2 ) begin
reqs <= 0;
end
end
assign vram_cpu_ack = reqs[ 0 ] && vram_ack2;
// Sprite data RAM, 256 x 8-bit words.
// Synchronous inputs and outputs.
reg [ 7 : 0 ] spr_addr;
wire spr_wren;
wire [ 7 : 0 ] spr_rdata;
vdp_sprdata sprdata(
spr_addr,
clk40m,
vram_rdata,
spr_wren,
spr_rdata
);
// Sprite data RAM address.
always @( col or scan_line or reqs or vrep or ghcount or spr_num ) begin
if( col < 32 ) begin
// Write data address.
spr_addr[ 7 : 2 ] = { !scan_line[ 0 ], spr_num };
spr_addr[ 1 : 0 ] = 2'bXX;
if( reqs[ 5 ] ) begin
spr_addr[ 1 : 0 ] = 2'b00; // Pattern 0.
end else if( reqs[ 4 ] ) begin
spr_addr[ 1 : 0 ] = 2'b01; // Pattern 1.
end else if( reqs[ 6 ] ) begin
spr_addr[ 1 : 0 ] = 2'b10; // Early clock/color.
end else if( reqs[ 8 ] ) begin
spr_addr[ 1 : 0 ] = 2'b11; // Horizontal position.
end
end else begin
// Read data address.
spr_addr = { ( vrep[ 1 ] ^ scan_line[ 0 ] ), col[ 2 : 0 ], ghcount[ 3 : 0 ] };
end
end
// Sprite RAM data write enable.
assign spr_wren = vram_ack2 && ( reqs[ 4 ] || reqs[ 5 ] || reqs[ 6 ] || reqs[ 8 ] );
// Sprite arithmetic.
wire [ 5 : 0 ] spr_side = spr_size ? ( spr_mag ? 6'd32 : 6'd16 ) : ( spr_mag ? 6'd16 : 6'd8 );
wire [ 7 : 0 ] spr_min = vram_rdata;
wire [ 7 : 0 ] spr_max = vram_rdata + spr_side;
wire spr_start = ( h == `H_WIDTH-1+`BORD_GRAPH-32*3-4 );
wire spr_setup = ( spr_start && vrep == 2 );
// Store VRAM read data.
reg [ 7 : 0 ] name;
reg [ 7 : 0 ] pattern;
reg [ 7 : 0 ] color;
reg [ 4 : 0 ] spr_line;
reg [ 7 : 0 ] spr_name;
reg spr_5;
reg [ 4 : 0 ] spr_5num;
reg [ 2 : 0 ] spr_count;
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
spr_5 <= 0;
end else if( start_vblank ) begin
spr_5 <= 0;
end else if( spr_setup ) begin
spr_load <= 1;
spr_xvld <= 0;
spr_count <= 0;
end else if( vram_ack2 ) begin
if( reqs[ 3 ] ) begin
name <= vram_rdata;
end
if( reqs[ 2 ] ) begin
if( !multi_mode ) begin
pattern <= vram_rdata;
end else begin
pattern <= 8'hF0;
color <= vram_rdata;
end
end
if( reqs[ 1 ] ) begin
color <= vram_rdata;
end
if( reqs[ 9 ] ) begin
if( vram_rdata == 208 ) begin
spr_load <= 0;
end else if( !spr_nolimit && spr_count == 4 ) begin
if( !spr_5 ) begin
spr_5num <= spr_num;
end
spr_5 <= 1;
spr_load <= 0;
end else if( scan_next+8'd32 > spr_min+8'd32 && scan_next+8'd32 <= spr_max+8'd32 ) begin
spr_xvld[ spr_num ] <= 1;
spr_line <= scan_next[ 4 : 0 ]-vram_rdata[ 4 : 0 ]-5'h1;
spr_count <= spr_count + 1'b1;
end
end
if( reqs[ 7 ] ) begin
spr_name <= vram_rdata;
end
end
end
// VRAM address MUX. Big MUX.
reg [ 13 : 0 ] vram_addr;
always @( xreq or vram_cpu_a
or nreq or ntb or text_mode or row or col
or preq or g1_mode or g2_mode or multi_mode or pgb or name or line or multi_row or text_pos
or creq or colb or vram_rdata
or svreq or shreq or snreq or sareq or sp0req or sp1req
or spr_num or spr_size or spr_mag or spr_name or spr_line or spgb or sab ) begin
vram_addr = 14'hXXXX;
if( xreq ) begin
vram_addr = vram_cpu_a;
end
if( nreq ) begin
vram_addr[ 13 : 10 ] = ntb;
if( text_mode ) begin
vram_addr[ 9 : 0 ] = text_pos;
end else begin
vram_addr[ 9 : 0 ] = { row, col[ 4 : 0 ] };
end
end
if( preq ) begin
// Name is not set until next clock, use vram_rdata instead.
if( g1_mode || text_mode ) begin
vram_addr = { pgb, vram_rdata, line };
end else if( g2_mode ) begin
vram_addr = { pgb[ 2 ], row[ 4 : 3 ], vram_rdata, line };
end else if( multi_mode ) begin
vram_addr = { pgb, vram_rdata, multi_row[ 2 : 0 ] };
end
end
if( creq ) begin
if( g1_mode ) begin
vram_addr = { colb, 1'b0, name[ 7 : 3 ] };
end else if( g2_mode ) begin
vram_addr = { colb[ 2 ], row[ 4 : 3 ], name, line };
end
end
if( svreq ) begin
vram_addr = { sab, spr_num, 2'b00 };
end
if( shreq ) begin
vram_addr = { sab, spr_num, 2'b01 };
end
if( snreq ) begin
vram_addr = { sab, spr_num, 2'b10 };
end
if( sareq ) begin
vram_addr = { sab, spr_num, 2'b11 };
end
if( sp0req ) begin
if( !spr_size ) begin
if( !spr_mag ) begin
vram_addr = { spgb, spr_name, spr_line[ 2 : 0 ] };
end else begin
vram_addr = { spgb, spr_name, spr_line[ 3 : 1 ] };
end
end else begin
if( !spr_mag ) begin
vram_addr = { spgb, spr_name[ 7 : 2 ], 1'b0, spr_line[ 3 : 0 ] };
end else begin
vram_addr = { spgb, spr_name[ 7 : 2 ], 1'b0, spr_line[ 4 : 1 ] };
end
end
end
if( sp1req ) begin
if( !spr_mag ) begin
vram_addr = { spgb, spr_name[ 7 : 2 ], 1'b1, spr_line[ 3 : 0 ] };
end else begin
vram_addr = { spgb, spr_name[ 7 : 2 ], 1'b1, spr_line[ 4 : 1 ] };
end
end
end
// Sprite state machine.
integer i;
reg spr_found;
reg spr_active;
reg [ 1 : 0 ] spr_hrep;
reg spr_pattern;
reg [ 3 : 0 ] spr_color;
reg spr_collide;
reg [ 8 : 0 ] spr_hcount;
reg [ 31 : 0 ] spr_odd;
reg [ 4 : 0 ] snum;
reg early;
reg [ 31 : 0 ] spr_vld;
reg [ 3 : 0 ] spr_col [ 0 : 31 ];
reg [ 8 : 0 ] spr_dly [ 0 : 31 ];
reg [ 15 : 0 ] spr_pat [ 0 : 31 ];
always @( negedge rst_n or posedge clk40m ) begin
if( !rst_n ) begin
spr_collide <= 0;
spr_active <= 0;
end else if( start_vblank ) begin
spr_collide <= 0;
spr_vld <= 0;
end else if( col >= 32 && col < 40 && ghcount >= 2 && ghcount < 18 ) begin
spr_active <= 0;
// Unload sprite data from RAM.
snum = ( ( ghcount - 2'd2 ) & 4'b1100 ) >> 2;
snum[ 4 : 2 ] = col[ 2 : 0 ];
case( ghcount[ 1 : 0 ] )
0: begin
spr_col[ snum ] <= spr_rdata[ 3 : 0 ];
early <= spr_rdata[ 7 ];
end
1: begin
if( early ) begin
spr_dly[ snum ] <= { 1'b0, spr_rdata };
end else begin
spr_dly[ snum ] <= 9'd32 + spr_rdata;
end
end
2: begin
spr_pat[ snum ][ 15 : 8 ] <= spr_rdata;
end
3: begin
if( spr_size ) begin
spr_pat[ snum ][ 7 : 0 ] <= spr_rdata;
end else begin
spr_pat[ snum ][ 7 : 0 ] <= 8'h00;
end
end
endcase
end else if( spr_start ) begin
spr_hrep <= 2;
spr_pattern <= 0;
spr_odd <= 0;
spr_hcount <= 0;
spr_active <= 1;
if( spr_setup ) begin
spr_vld <= spr_xvld;
end
end else if( spr_active && spr_hrep == 1 ) begin
// Multicycle path:
// Count, pattern, color, collide clocked in when hrep 1->2 only.
spr_found = 0;
for( i = 0; i <= 31; i = i + 1 ) begin
if( spr_hcount >= spr_dly[ i ] ) begin
if( spr_vld[ i ] ) begin
if( spr_pat[ i ][ 15 ] ) begin
if( spr_found ) begin
// Sprite collision.
spr_collide <= 1;
end else begin
spr_color <= spr_col[ i ];
end
spr_found = 1;
end
if( !spr_mag || spr_odd[ i ] ) begin
// Shift pattern, zero backfill.
spr_pat[ i ] <= { spr_pat[ i ][ 14 : 0 ], 1'b0 };
end
end
spr_odd[ i ] <= ~spr_odd[ i ];
end
end
spr_pattern <= spr_found;
spr_hrep <= 2;
spr_hcount <= spr_hcount + 1'b1;
end else if( spr_hrep == 2 ) begin
spr_hrep <= 0;
end else begin
spr_hrep <= 1;
end
end
endmodule