1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-06 16:14:42 +00:00
Files
mist-devel.mist-board/cores/mist/shifter.v

723 lines
25 KiB
Verilog

//
// shifter.v
//
// Atari ST(E) shifter implementation for the MiST board
// http://code.google.com/p/mist-board/
//
// Copyright (c) 2013-2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
module shifter (
// system interface
input clk, // 32.000 MHz
input [1:0] bus_cycle, // bus-cycle for sync
// memory interface
output reg [22:0] vaddr, // video word address counter
output read, // video read cycle
input [63:0] data, // video data read
// cpu register interface
input cpu_clk,
input cpu_reset,
input [15:0] cpu_din,
input cpu_sel,
input [5:0] cpu_addr,
input cpu_uds,
input cpu_lds,
input cpu_rw,
output reg [15:0] cpu_dout,
// screen interface
output reg hs, // H_SYNC
output reg vs, // V_SYNC
output reg [3:0] video_r, // Red
output reg [3:0] video_g, // Green
output reg [3:0] video_b, // Blue
// system config
input pal56, // use VGA compatible 56hz for PAL
input ste, // enable STE featurss
output vga_hs_pol, // sync polarity to be used on vga
output vga_vs_pol,
output clk_16, // 16Mhz clock for scan doubler
// signals not affected by scan doubler for internal use like irqs
output st_de,
output reg st_vs,
output reg st_hs
);
localparam STATE_SYNC = 2'd0;
localparam STATE_BLANK = 2'd1;
localparam STATE_BORDER = 2'd2;
localparam STATE_DISP = 2'd3;
// ---------------------------------------------------------------------------
// --------------------------- internal state counter ------------------------
// ---------------------------------------------------------------------------
reg [1:0] t;
always @(posedge clk) begin
// 32Mhz counter synchronous to 8 Mhz clock
// force counter to pass state 0 exactly after the rising edge of clk_reg (8Mhz)
if(((t == 2'd3) && ( cpu_clk == 0)) ||
((t == 2'd0) && ( cpu_clk == 1)) ||
((t != 2'd3) && (t != 2'd0)))
t <= t + 2'd1;
end
// give 16Mhz clock to scan doubler
assign clk_16 = t[0];
// create internal bus_cycle signal which is stable on the positive clock
// edge and extends the previous state by half a 32 Mhz clock cycle
reg [3:0] bus_cycle_L;
always @(negedge clk)
bus_cycle_L <= { bus_cycle, t };
// ---------------------------------------------------------------------------
// ------------------------------ internal signals ---------------------------
// ---------------------------------------------------------------------------
// st_de is the internal display enable signal as used by the mfp. This is used
// by software to generate a line interrupt and to e.g. do 512 color effects.
// st_de is active low. Using display enable (de) for this makes sure the cpu has
// plenty of time before data for the next line is starting to be fetched
// assign st_de = ~de;
// according to hatari video.h/video.c the timer_b irq comes 28 8MHz cycles after
// the last pixel has been displayed. Our de is 4 cycles earlier then the end
// of the line, so we delay by 28+4=32
reg [31:0] st_de_delay;
assign st_de = st_de_delay[31];
always @(posedge t[1])
st_de_delay <= { st_de_delay[30:0], ~de };
always @(posedge clk) begin
st_hs <= h_sync;
// vsync irq is generated right after the last border line has been displayed
// According to hatari vbl happens in cycle 64. Display starts at cycle 56,
// so vbl happens at cycle 8 of the display phase
if(hcnt == 8) begin
// vsync starts at begin of blanking phase
if(vcnt == t7_v_blank_bot) st_vs <= 1'b1;
// vsync ends at begin of top border
if(vcnt == t10_v_border_top) st_vs <= 1'b0;
end
end
// ---------------------------------------------------------------------------
// -------------------------------- video mode -------------------------------
// ---------------------------------------------------------------------------
wire [121:0] config_string;
video_modes video_modes(
// signals used to select the appropriate mode
.mono (mono),
.pal (pal),
.pal56 (pal56),
// resulting string containing timing values
.mode_str (config_string)
);
// The video config string contains 12 counter values (tX), six for horizontal
// timing and six for vertical timing. Each value has 10 bits, the total string
// is thus 120 bits long + space for some additional info like sync polarity
// display border blank(FP) sync blank(BP) border
// |--------------------|xxxxxx|#########|_______|##########|xxxxxx|
// t0 t1 t2 t3 t4 t5 horizontal
// t6 t7 t8 t9 t10 t11 vertical
// extract the various timing parameters from the config string
// horizontal timing values are for 640 pixel and are divided by 2 for 320 pixel low rez
assign vga_hs_pol = config_string[121];
wire [9:0] t0_h_border_right = low?{1'b0,config_string[120:112]}:config_string[120:111];
wire [9:0] t1_h_blank_right = low?{1'b0,config_string[110:102]}:config_string[110:101];
wire [9:0] t2_h_sync = low?{1'b0,config_string[100:92]}:config_string[100:91];
wire [9:0] t3_h_blank_left = low?{1'b0,config_string[90:82]}:config_string[90:81];
wire [9:0] t4_h_border_left = low?{1'b0,config_string[80:72]}:config_string[80:71];
wire [9:0] t5_h_end = low?{1'b0,config_string[70:62]}:config_string[70:61];
assign vga_vs_pol = config_string[60];
wire [9:0] t6_v_border_bot = config_string[59:50];
wire [9:0] t7_v_blank_bot = config_string[49:40];
wire [9:0] t8_v_sync = config_string[39:30];
wire [9:0] t9_v_blank_top = config_string[29:20];
wire [9:0] t10_v_border_top = config_string[19:10];
wire [9:0] t11_v_end = config_string[9:0];
// default video mode is monochrome
parameter DEFAULT_MODE = 3'd2;
// shiftmode register
reg [1:0] shmode;
wire mono = (shmode == 2'd2);
wire mid = (shmode == 2'd1);
wire low = (shmode == 2'd0);
// derive number of planes from shiftmode
wire [2:0] planes = mono?3'd1:(mid?3'd2:3'd4);
reg [1:0] syncmode;
reg [1:0] syncmode_latch;
wire pal = (syncmode_latch[1] == 1'b1);
// data input buffers for up to 4 planes
reg [15:0] data_latch[4];
localparam BASE_ADDR = 23'h8000; // default video base address 0x010000
reg [22:0] _v_bas_ad; // video base address register
// 16 colors with 3*4 bits each (4 bits for STE, ST only uses 3 bits)
reg [3:0] palette_r[15:0];
reg [3:0] palette_g[15:0];
reg [3:0] palette_b[15:0];
// STE-only registers
reg [7:0] line_offset; // number of words to skip at the end of each line
reg [3:0] pixel_offset; // number of pixels to skip at begin of line
reg ste_overscan_enable; // STE has a special 16 bit overscan
// ---------------------------------------------------------------------------
// ----------------------------- CPU register read ---------------------------
// ---------------------------------------------------------------------------
always @(cpu_sel, cpu_rw, cpu_uds, cpu_lds, cpu_addr, _v_bas_ad, shmode, vaddr,
syncmode, line_offset, pixel_offset, ste) begin
cpu_dout = 16'h0000;
// read registers
if(cpu_sel && cpu_rw) begin
// video base register (r/w)
if(cpu_addr == 6'h00) cpu_dout <= { 8'h00, _v_bas_ad[22:15] };
if(cpu_addr == 6'h01) cpu_dout <= { 8'h00, _v_bas_ad[14: 7] };
if(ste && cpu_addr == 6'h06) cpu_dout <= { 8'h00, _v_bas_ad[ 6: 0], 1'b0 };
// video address counter (ro on ST)
if(cpu_addr == 6'h02) cpu_dout <= { 8'h00, vaddr[22:15] };
if(cpu_addr == 6'h03) cpu_dout <= { 8'h00, vaddr[14:7 ] };
if(cpu_addr == 6'h04) cpu_dout <= { 8'h00, vaddr[6:0], 1'b0 };
// syncmode register
if(cpu_addr == 6'h05) cpu_dout <= { 6'h00, syncmode, 8'h00 };
if(ste) begin
if(cpu_addr == 6'h07) cpu_dout <= { 8'h00, line_offset };
if(cpu_addr == 6'h32) cpu_dout <= { 12'h000, pixel_offset };
end
// the color palette registers
if(cpu_addr >= 6'h20 && cpu_addr < 6'h30 ) begin
cpu_dout[3:0] <= palette_b[cpu_addr[3:0]];
cpu_dout[7:4] <= palette_g[cpu_addr[3:0]];
cpu_dout[11:8] <= palette_r[cpu_addr[3:0]];
// return only the 3 msb in non-ste mode
if(!ste) begin
cpu_dout[3] <= 1'b0;
cpu_dout[7] <= 1'b0;
cpu_dout[11] <= 1'b0;
end
end
// shift mode register
if(cpu_addr == 6'h30) cpu_dout <= { 6'h00, shmode, 8'h00 };
end
end
// ---------------------------------------------------------------------------
// ----------------------------- CPU register write --------------------------
// ---------------------------------------------------------------------------
// STE video address write signal is evaluated inside memory engine
wire ste_vaddr_write = ste && cpu_sel && !cpu_rw && !cpu_lds;
always @(negedge cpu_clk) begin
if(cpu_reset) begin
_v_bas_ad <= BASE_ADDR;
shmode <= DEFAULT_MODE; // default video mode 2 => mono
syncmode <= 2'b00; // 60hz
// disable STE hard scroll features
line_offset <= 8'h00;
pixel_offset <= 4'h0;
ste_overscan_enable <= 1'b0;
palette_b[ 0] <= 4'b111;
end else begin
// write registers
if(cpu_sel && !cpu_rw) begin
if(!cpu_lds) begin
// video base address hi/mid (ST and STE)
if(cpu_addr == 6'h00) _v_bas_ad[22:15] <= cpu_din[7:0];
if(cpu_addr == 6'h01) _v_bas_ad[14:7] <= cpu_din[7:0];
// In the STE setting hi or mid clears the low byte for ST compatibility
// in ST mode this doesn't harm
if(cpu_addr[5:1] == 5'h00) _v_bas_ad[6:0] <= 7'h00;
// the low byte can only be written in STE mode
if(ste && cpu_addr == 6'h06) _v_bas_ad[6:0] <= cpu_din[7:1];
end
// writing to sync mode toggles between 50 and 60 hz modes
if(cpu_addr == 6'h05 && !cpu_uds) syncmode <= cpu_din[9:8];
// writing special STE registers
if(ste && !cpu_lds) begin
if(cpu_addr == 6'h07) line_offset <= cpu_din[7:0];
if(cpu_addr == 6'h32) begin
pixel_offset <= cpu_din[3:0];
ste_overscan_enable <= 1'b0;
end
// Writing the video address counter happens directly inside the
// memory engine further below!!!
end
// byte write of 0 to ff8264 while ff8365 (pixel_offset) != 0 results in extra
// ste overscan
if(ste && !cpu_uds && cpu_lds) begin
if((cpu_addr == 6'h32) && (pixel_offset != 0))
ste_overscan_enable <= 1'b1;
end
// the color palette registers, always write bit 3 with zero if not in
// ste mode as this is the lsb of ste
if(cpu_addr >= 6'h20 && cpu_addr < 6'h30 ) begin
if(!cpu_uds) begin
if(!ste) palette_r[cpu_addr[3:0]] <= { 1'b0 , cpu_din[10:8] };
else palette_r[cpu_addr[3:0]] <= cpu_din[11:8];
end
if(!cpu_lds) begin
if(!ste) begin
palette_g[cpu_addr[3:0]] <= { 1'b0, cpu_din[6:4] };
palette_b[cpu_addr[3:0]] <= { 1'b0, cpu_din[2:0] };
end else begin
palette_g[cpu_addr[3:0]] <= cpu_din[7:4];
palette_b[cpu_addr[3:0]] <= cpu_din[3:0];
end
end
end
// make msb writeable if MiST video modes are enabled
if(cpu_addr == 6'h30 && !cpu_uds) shmode <= cpu_din[9:8];
end
end
end
// ---------------------------------------------------------------------------
// -------------------------- video signal generator -------------------------
// ---------------------------------------------------------------------------
// ----------------------- monochrome video signal ---------------------------
// mono uses the lsb of blue palette entry 0 to invert video
wire [3:0] blue0 = palette_b[0];
wire mono_bit = blue0[0]^shift_0[15];
wire [3:0] mono_rgb = { mono_bit, mono_bit, mono_bit, mono_bit };
// ------------------------- colour video signal -----------------------------
// For ST compatibility reasons the STE has the color bit order 0321. This is
// handled here
wire [3:0] color_index = border?4'd0:{ shift_3[15], shift_2[15], shift_1[15], shift_0[15] };
wire [3:0] color_r_pal = palette_r[color_index];
wire [3:0] color_r = { color_r_pal[2:0], color_r_pal[3] };
wire [3:0] color_g_pal = palette_g[color_index];
wire [3:0] color_g = { color_g_pal[2:0], color_g_pal[3] };
wire [3:0] color_b_pal = palette_b[color_index];
wire [3:0] color_b = { color_b_pal[2:0], color_b_pal[3] };
// --------------- de-multiplex color and mono into one vga signal -----------
wire [3:0] stvid_r = mono?mono_rgb:color_r;
wire [3:0] stvid_g = mono?mono_rgb:color_g;
wire [3:0] stvid_b = mono?mono_rgb:color_b;
// shift registers for up to 4 planes
reg [15:0] shift_0, shift_1, shift_2, shift_3;
// clock divider to generate the mid and low rez pixel clocks
wire pclk = low?t[1]:mid?t[0]:clk;
// use variable dot clock
always @(posedge pclk) begin
hs <= ~h_sync;
vs <= ~v_sync;
// drive video output
video_r <= blank?4'b0000:stvid_r;
video_g <= blank?4'b0000:stvid_g;
video_b <= blank?4'b0000:stvid_b;
// shift all planes and reload
// shift registers every 16 pixels
if((hcnt[3:0] == 4'hf)||(hcnt == t5_h_end)) begin
if(!ste || (pixel_offset == 0) || ste_overscan_enable) begin
shift_0 <= data_latch[0];
shift_1 <= data_latch[1];
shift_2 <= data_latch[2];
shift_3 <= data_latch[3];
end else begin
shift_0 <= ste_shifted_0;
shift_1 <= ste_shifted_1;
shift_2 <= ste_shifted_2;
shift_3 <= ste_shifted_3;
end
end else begin
shift_0 <= { shift_0[14:0], 1'b0 };
shift_1 <= { shift_1[14:0], 1'b0 };
shift_2 <= { shift_2[14:0], 1'b0 };
shift_3 <= { shift_3[14:0], 1'b0 };
end
end
// ---------------------------------------------------------------------------
// ----------------------------- overscan detection --------------------------
// ---------------------------------------------------------------------------
// Currently only opening the bottom border for overscan is supported. Opening
// the top border should also be easy. Opening the side borders is basically
// impossible as this requires a 100% perfect CPU and shifter timing.
reg last_syncmode;
reg [3:0] bottom_overscan_cnt;
reg [3:0] top_overscan_cnt;
wire bottom_overscan = (bottom_overscan_cnt != 0);
wire top_overscan = (top_overscan_cnt != 0);
reg syncmode_at_line_start;
always @(posedge clk) begin
if(cpu_reset) begin
top_overscan_cnt <= 4'd0;
bottom_overscan_cnt <= 4'd0;
end else begin
last_syncmode <= syncmode[1]; // delay syncmode to detect changes
// reset counters
if((vcnt == 0) && (hcnt == 10'd0)) begin
if(bottom_overscan_cnt != 0)
bottom_overscan_cnt <= bottom_overscan_cnt - 4'd1;
if(top_overscan_cnt != 0)
top_overscan_cnt <= top_overscan_cnt - 4'd1;
end
// this is the magic used to do "overscan".
// the magic actually involves more than toggling syncmode (50/60hz)
// within line 200. But this is sufficient for our detection
// trigger in line 199
if( (vcnt >= 9'd194) && (vcnt <= 9'd202) ) begin
// syncmode has changed between 1 and 0 (50/60 hz)
if(syncmode[1] != last_syncmode)
bottom_overscan_cnt <= 4'd15;
end
// trigger in line 284/285 and 1/2
if( ( (vcnt >= 9'd280) && (vcnt <= 9'd290) ) ||
( (vcnt >= 9'd0) && (vcnt <= 9'd3) ) ) begin
// syncmode has changed between 1 and 0 (50/60 hz)
if(syncmode[1] != last_syncmode)
top_overscan_cnt <= 4'd15;
end
// latch syncmode at begin of line and check if it changed at the end
// this special case causes 2 less (50->60) or more (60->50) bytes to be read
// during a line
if(hcnt == de_h_start)
syncmode_at_line_start <= syncmode[1];
end
end
// ---------------------------------------------------------------------------
// --------------------------- STE hard scroll shifter -----------------------
// ---------------------------------------------------------------------------
// When STE hard scrolling is being used (pixel_offset != 0) then memory reading starts
// 16 pixels earlier and data is being moved through an additional shift register
// extra 32 bit registers required for STE hard scrolling
reg [31:0] ste_shift_0, ste_shift_1, ste_shift_2, ste_shift_3;
// shifted data
wire [15:0] ste_shifted_0, ste_shifted_1, ste_shifted_2, ste_shifted_3;
// connect STE scroll shifters for each plane
ste_shifter ste_shifter_0 (
.skew (pixel_offset),
.in (ste_shift_0),
.out (ste_shifted_0)
);
ste_shifter ste_shifter_1 (
.skew (pixel_offset),
.in (ste_shift_1),
.out (ste_shifted_1)
);
ste_shifter ste_shifter_2 (
.skew (pixel_offset),
.in (ste_shift_2),
.out (ste_shifted_2)
);
ste_shifter ste_shifter_3 (
.skew (pixel_offset),
.in (ste_shift_3),
.out (ste_shifted_3)
);
// move data into STE hard scroll shift registers
always @(posedge clk) begin
if((bus_cycle_L == 4'd08) && (plane == 2'd0)) begin
// shift up 16 pixels and load new data into lower bits of shift registers
ste_shift_0 <= { ste_shift_0[15:0], data_latch[0] };
ste_shift_1 <= { ste_shift_1[15:0], (planes > 3'd1)?data_latch[1]:16'h0000 };
ste_shift_2 <= { ste_shift_2[15:0], (planes > 3'd2)?data_latch[2]:16'h0000 };
ste_shift_3 <= { ste_shift_3[15:0], (planes > 3'd2)?data_latch[3]:16'h0000 };
end
end
// ---------------------------------------------------------------------------
// ------------------------------- memory engine -----------------------------
// ---------------------------------------------------------------------------
assign read = (bus_cycle == 0) && de; // display enable can directly be used as a ram read signal
// current plane to be read from memory
reg [1:0] plane;
// To be able to output the first pixel we need to have one word for every plane already
// present in memory. We thus need a display enable signal which is (depending on color depth)
// 16, 32 or 64 pixel ahead of display enable
reg de, de_v;
// required pixel offset allowing for prefetch of 16 pixels
wire [9:0] ste_overscan = ste_overscan_enable?10'd16:10'd0;
// ste is starting another 16 pixels earlier if horizontal hard scroll is being used
wire [9:0] ste_prefetch = (ste && ((pixel_offset != 0) && !ste_overscan_enable))?10'd16:10'd0;
wire [9:0] de_h_start = t5_h_end - 10'd16 - ste_prefetch;
wire [9:0] de_h_end = t0_h_border_right - 10'd16 + ste_overscan;
// extra lines required by vertical overscan
wire [9:0] de_v_top_extra = top_overscan?10'd29:10'd0 /* synthesis keep */; // 29 extra ST lines at top
wire [9:0] de_v_bot_extra = bottom_overscan?10'd38:10'd0 /* synthesis keep */; // 38 extra ST lines at bottom
// calculate lines in which active display starts end ends
wire [9:0] de_v_start = t11_v_end - de_v_top_extra;
wire [9:0] de_v_end = t6_v_border_bot + de_v_bot_extra;
always @(posedge clk) begin
// line in which memory access is enabled
if(hcnt == v_event) begin
if(vcnt == de_v_start) de_v <= 1'b1;
if(vcnt == de_v_end) de_v <= 1'b0;
end
// display enable signal 16/32/64 bits (16*planes) ahead of display enable (de)
// include bus cycle to stay in sync in scna doubler mode
if(de_v) begin
if(hcnt == de_h_start) de <= 1'b1;
if(hcnt == de_h_end) de <= 1'b0;
end
// according to hatari the video counter is reloaded 3 lines before
// the vbi occurs. This is right after the display has been painted.
// The video address counter is reloaded right after display ends
// also according to hatari this happens 8 cycles before display starts
// in mono mode it doesn't work that way as there's no border and
// three lines before blank is inside the display area
if((hcnt == t5_h_end-8 ) &&
(vcnt == (mono?(t7_v_blank_bot+10'd1):(t7_v_blank_bot-10'd3)))) begin
vaddr <= _v_bas_ad;
plane <= 2'd0;
// copy syncmode
syncmode_latch <= syncmode;
end else begin
// video transfer happens in cycle 3 (end of video cycle)
if(bus_cycle_L == 3) begin
// read if display enable is active
if(de) begin
// move incoming video data into data latch
// ST shifter only uses 16 out of possible 64 bits, so select the right word
case(vaddr[1:0])
2'd0: data_latch[plane] <= data[15: 0];
2'd1: data_latch[plane] <= data[31:16];
2'd2: data_latch[plane] <= data[47:32];
2'd3: data_latch[plane] <= data[63:48];
endcase
// advance video address
vaddr <= vaddr + 23'd1;
// advance plane counter
if(planes != 1) begin
plane <= plane + 2'd1;
if(plane == planes - 2'd1)
plane <= 2'd0;
end
end
end
end
// at the end of each line check whether someone messed with the
// syncmode register in a way that the line ends at adifferent mode than
// it started
if(de_v && (hcnt == de_h_end + 1 ) && (t == 0)) begin
if(syncmode_at_line_start && !syncmode[1]) // 50->60 Hz => two bytes less
vaddr <= vaddr - 23'd1; // two bytes less
if(!syncmode_at_line_start && syncmode[1]) // 60->50 Hz => two bytes less
vaddr <= vaddr + 23'd1; // two bytes more
end
// STE has additional ways to influence video address
if(ste) begin
// add line offset at the end of each video line
if(de_v && (hcnt == de_h_end) && (t == 0))
vaddr <= vaddr + line_offset;
// STE vaddr write handling
// bus_cycle 6 is in the middle of a cpu cycle
if((bus_cycle_L == 6) && ste_vaddr_write) begin
if(cpu_addr == 6'h02) vaddr[22:15] <= cpu_din[7:0];
if(cpu_addr == 6'h03) vaddr[14: 7] <= cpu_din[7:0];
if(cpu_addr == 6'h04) vaddr[ 6: 0] <= cpu_din[7:1];
end
end
end
// ---------------------------------------------------------------------------
// ------------------------- video timing generator --------------------------
// ---------------------------------------------------------------------------
reg [9:0] hcnt; // horizontal pixel counter
reg [1:0] h_state; // 0=sync, 1=blank, 2=border, 3=display
// A seperate vertical timing is not needed, vcnt[9:1] is the st line
reg [9:0] vcnt; // vertical line counter
reg [1:0] v_state; // 0=sync, 1=blank, 2=border, 3=display
// blank level is also used during sync
wire blank = (v_state == STATE_BLANK) || (h_state == STATE_BLANK) ||
(v_state == STATE_SYNC) || (h_state == STATE_SYNC);
// only the color modes use the border
wire border = (v_state == STATE_BORDER)||(h_state == STATE_BORDER);
// time in horizontal timing where vertical states change (at the begin of the sync phase)
wire [9:0] v_event = t2_h_sync;
reg v_sync, h_sync;
always @(posedge pclk) begin
// ------------- horizontal ST timing generation -------------
// Run st timing at full speed if no scan doubler is being used. Otherwise run
// it at half speed
if(hcnt == t5_h_end) begin
// sync hcnt to bus
if((low && (bus_cycle_L[3:2] == 2'b11)) ||
(mid && (bus_cycle_L[3:1] == 3'b111)) ||
(mono && (bus_cycle_L[3:0] == 4'b1111)))
hcnt <= 10'd0;
end else
hcnt <= hcnt + 10'd1;
if( hcnt == t2_h_sync) h_sync <= 1'b1;
if( hcnt == t3_h_blank_left) h_sync <= 1'b0;
// generate horizontal video signal states
if( hcnt == t2_h_sync ) h_state <= STATE_SYNC;
if((hcnt == t0_h_border_right + ste_overscan) ||
(hcnt == t4_h_border_left)) h_state <= STATE_BORDER;
if((hcnt == t1_h_blank_right) || (hcnt == t3_h_blank_left)) h_state <= STATE_BLANK;
if( hcnt == t5_h_end) h_state <= STATE_DISP;
// vertical state changes at begin of hsync
if(hcnt == v_event) begin
// ------------- vertical timing generation -------------
// increase vcnt
if(vcnt == t11_v_end) vcnt <= 10'd0;
else vcnt <= vcnt + 10'd1;
if( vcnt == t8_v_sync) v_sync <= 1'b1;
if( vcnt == t9_v_blank_top) v_sync <= 1'b0;
// generate vertical video signal states
if( vcnt == t8_v_sync ) v_state <= STATE_SYNC;
if((vcnt == de_v_end) || (vcnt == t10_v_border_top)) v_state <= STATE_BORDER;
if((vcnt == t7_v_blank_bot) || (vcnt == t9_v_blank_top)) v_state <= STATE_BLANK;
if( vcnt == de_v_start) v_state <= STATE_DISP;
end
end
endmodule
// ---------------------------------------------------------------------------
// --------------------------- STE hard scroll shifter -----------------------
// ---------------------------------------------------------------------------
module ste_shifter (
input [3:0] skew,
input [31:0] in,
output reg [15:0] out
);
always @(skew, in) begin
out = 16'h0000;
case(skew)
15: out = in[16:1];
14: out = in[17:2];
13: out = in[18:3];
12: out = in[19:4];
11: out = in[20:5];
10: out = in[21:6];
9: out = in[22:7];
8: out = in[23:8];
7: out = in[24:9];
6: out = in[25:10];
5: out = in[26:11];
4: out = in[27:12];
3: out = in[28:13];
2: out = in[29:14];
1: out = in[30:15];
0: out = in[31:16];
endcase; // case (skew)
end
endmodule