diff --git a/cores/mist/mist.qsf b/cores/mist/mist.qsf index 99b032b..390a7de 100644 --- a/cores/mist/mist.qsf +++ b/cores/mist/mist.qsf @@ -323,6 +323,9 @@ set_global_assignment -name VERILOG_FILE clock.v set_global_assignment -name VERILOG_FILE mist_top.v set_global_assignment -name VERILOG_FILE user_io.v set_global_assignment -name VERILOG_FILE video.v +set_global_assignment -name VERILOG_FILE shifter.v +set_global_assignment -name VERILOG_FILE scandoubler.v +set_global_assignment -name VERILOG_FILE sync_adjust.v set_global_assignment -name VERILOG_FILE viking.v set_global_assignment -name VERILOG_FILE video_modes.v set_global_assignment -name VERILOG_FILE io_fifo.v diff --git a/cores/mist/mist_top.v b/cores/mist/mist_top.v index 77b7ef8..b9d9292 100644 --- a/cores/mist/mist_top.v +++ b/cores/mist/mist_top.v @@ -240,7 +240,7 @@ wire [15:0] io_data_out = vreg_data_out | dma_data_out | blitter_data_out | wire init = ~pll_locked; -/* -------------------------- Viking video card -------------------- */ +/* -------------------------- Video subsystem -------------------- */ // viking/sm194 is enabled and max 8MB memory may be enabled. In steroids mode // video memory is moved to $e80000 and all stram up to 14MB may be used @@ -263,79 +263,47 @@ always @(posedge clk_32) begin viking_in_use <= viking_in_use + 8'h01; end -// video output multiplexer to switch between shifter and viking -wire viking_active = (viking_in_use == 8'hff) && !osd_enable; -assign VGA_HS = viking_active?viking_hs:shifter_hs; -assign VGA_VS = viking_active?viking_vs:shifter_vs; -assign VGA_R = viking_active?viking_r:shifter_r; -assign VGA_G = viking_active?viking_g:shifter_g; -assign VGA_B = viking_active?viking_b:shifter_b; - -wire viking_hs, viking_vs; -wire [5:0] viking_r, viking_g, viking_b; - -wire [22:0] viking_address; -wire viking_read; - -viking viking ( - .pclk (clk_128 ), // pixel - .bus_cycle (bus_cycle ), - - // memory interface - .himem (steroids ), - .bclk (clk_8 ), // system bus clock = 8Mhz - .addr (viking_address ), - .data (ram_data_out_64), - .read (viking_read ), - - // video output - .hs (viking_hs ), - .vs (viking_vs ), - .r (viking_r ), - .g (viking_g ), - .b (viking_b ) -); - -wire osd_enable; -wire shifter_hs, shifter_vs; -wire [5:0] shifter_r, shifter_g, shifter_b; +wire viking_active = (viking_in_use == 8'hff); video video ( - .clk (clk_32 ), + .clk_128 (clk_128 ), + .clk_32 (clk_32 ), .bus_cycle (bus_cycle ), // spi for OSD .sdi (SPI_DI ), .sck (SPI_SCK ), .ss (SPI_SS3 ), - .osd_enable (osd_enable ), // cpu register interface - .reg_clk (clk_8 ), - .reg_reset (reset ), - .reg_din (tg68_dat_out), - .reg_sel (vreg_sel ), - .reg_addr (tg68_adr[6:1]), - .reg_uds (tg68_uds ), - .reg_lds (tg68_lds ), - .reg_rw (tg68_rw ), - .reg_dout (vreg_data_out), + .cpu_clk (clk_8 ), + .cpu_reset (reset ), + .cpu_din (tg68_dat_out), + .cpu_sel (vreg_sel ), + .cpu_addr (tg68_adr[6:1]), + .cpu_uds (tg68_uds ), + .cpu_lds (tg68_lds ), + .cpu_rw (tg68_rw ), + .cpu_dout (vreg_data_out), .vaddr (video_address ), .data (ram_data_out_64), .read (video_read ), - .hs (shifter_hs ), - .vs (shifter_vs ), - .video_r (shifter_r ), - .video_g (shifter_g ), - .video_b (shifter_b ), + .hs (VGA_HS ), + .vs (VGA_VS ), + .video_r (VGA_R ), + .video_g (VGA_G ), + .video_b (VGA_B ), // configuration signals - .adjust (video_adj ), - .pal56 (~system_ctrl[9]), - .scanlines (system_ctrl[21:20]), - .ste (ste ), + .viking_enable ( viking_active ), // enable and activate viking video card + .viking_himem ( steroids ), // let viking use memory from $e80000 + .scandoubler_disable ( scandoubler_disable ), // don't use scandoubler in 15khz modes + .adjust ( video_adj ), + .pal56 ( ~system_ctrl[9] ), + .scanlines ( system_ctrl[21:20] ), + .ste ( ste ), // signals not affected by scan doubler required for // irq generation @@ -1110,25 +1078,25 @@ wire second_cpu_slot = (mste && enable_16mhz) || steroids; // Two of the four cycles are being used. One for video (+STE audio) and one for // cpu, DMA and Blitter. A third is optionally being used for faster CPU -wire video_cycle = (bus_cycle == 0); +wire video_cycle = (bus_cycle == 0)||(bus_cycle == 2); wire cpu_cycle = (bus_cycle == 1) || (second_cpu_slot && (bus_cycle == 3)); -wire viking_cycle = (bus_cycle == 2); // ----------------- RAM address -------------- -wire [22:0] video_cycle_addr = (st_hs && ste)?ste_dma_snd_addr:video_address; +wire ste_dma_has_bus = (bus_cycle == 0) && st_hs && ste; +wire [22:0] video_cycle_addr = ste_dma_has_bus?ste_dma_snd_addr:video_address; wire [22:0] cpu_cycle_addr = dma_has_bus?dma_addr:(blitter_has_bus?blitter_master_addr:tg68_adr[23:1]); -wire [22:0] ram_address = viking_cycle?viking_address:(video_cycle?video_cycle_addr:cpu_cycle_addr); +wire [22:0] ram_address = video_cycle?video_cycle_addr:cpu_cycle_addr; // ----------------- RAM read ----------------- // memory access during the video cycle is shared between video and ste_dma_snd -wire video_cycle_oe = (st_hs && ste)?ste_dma_snd_read:video_read; +wire video_cycle_oe = ste_dma_has_bus?ste_dma_snd_read:video_read; // memory access during the cpu cycle is shared between blitter and cpu wire cpu_cycle_oe = dma_has_bus?dma_read:(blitter_has_bus?blitter_master_read:(cpu_cycle && tg68_as && tg68_rw && cpu2mem)); -wire ram_oe = viking_cycle?viking_read:(video_cycle?video_cycle_oe:(cpu_cycle?cpu_cycle_oe:1'b0)); +wire ram_oe = video_cycle?video_cycle_oe:(cpu_cycle?cpu_cycle_oe:1'b0); // ----------------- RAM write ----------------- wire cpu_cycle_wr = dma_has_bus?dma_write:(blitter_has_bus?blitter_master_write:(cpu_cycle && tg68_as && ~tg68_rw && cpu2ram)); -wire ram_wr = (viking_cycle||video_cycle)?1'b0:(cpu_cycle?cpu_cycle_wr:1'b0); +wire ram_wr = video_cycle?1'b0:(cpu_cycle?cpu_cycle_wr:1'b0); wire [15:0] ram_data_out; wire [15:0] system_data_out = cpu2mem?ram_data_out:io_data_out; @@ -1227,6 +1195,8 @@ wire eth_tx_read_strobe, eth_tx_read_begin; wire [7:0] eth_rx_write_byte; wire eth_rx_write_strobe, eth_rx_write_begin; +wire scandoubler_disable; + //// user io has an extra spi channel outside minimig core //// user_io user_io( // the spi interface @@ -1274,6 +1244,9 @@ user_io user_io( .eth_rx_write_begin (eth_rx_write_begin), .eth_rx_write_strobe (eth_rx_write_strobe), .eth_rx_write_byte (eth_rx_write_byte), + + // io controller requests to disable vga scandoubler + .scandoubler_disable (scandoubler_disable), .CORE_TYPE (8'ha3) // mist core id ); diff --git a/cores/mist/osd.v b/cores/mist/osd.v index 0cd0970..a2ef648 100644 --- a/cores/mist/osd.v +++ b/cores/mist/osd.v @@ -4,7 +4,7 @@ // On Screen Display implementation for the MiST board // http://code.google.com/p/mist-board/ // -// Copyright (c) 2013 Till Harbaum +// Copyright (c) 2015 Till Harbaum // // 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 @@ -21,32 +21,74 @@ // module osd ( - input clk, // 31.875 MHz + input clk, // 31.875 MHz // SPI interface for OSD - input sck, - input ss, - input sdi, + input sck, + input ss, + input sdi, - output reg osd_enable, - - // current video beam position - input [9:0] hcnt, - input [9:0] vcnt, + input hs, + input vs, - input [5:0] in_r, // Red[5:0] - input [5:0] in_g, // Green[5:0] - input [5:0] in_b, // Blue[5:0] + input [5:0] r_in, // Red[5:0] + input [5:0] g_in, // Green[5:0] + input [5:0] b_in, // Blue[5:0] - output [5:0] out_r, // Red[5:0] - output [5:0] out_g, // Green[5:0] - output [5:0] out_b // Blue[5:0] + output [5:0] r_out, // Red[5:0] + output [5:0] g_out, // Green[5:0] + output [5:0] b_out // Blue[5:0] ); -assign out_r = !oe?in_r:{osd_pixel, osd_pixel, osd_pixel, in_r[5:3]}; -assign out_g = !oe?in_g:{osd_pixel, osd_pixel, 1'b1, in_g[5:3]}; -assign out_b = !oe?in_b:{osd_pixel, osd_pixel, osd_pixel, in_b[5:3]}; +// combine input and OSD into a overlayed signal +assign r_out = !oe?r_in:{pixel, pixel, pixel, r_in[5:3]}; +assign g_out = !oe?g_in:{pixel, pixel, 1'b1, g_in[5:3]}; +assign b_out = !oe?b_in:{pixel, pixel, pixel, b_in[5:3]}; +reg enabled; + +// --------------------------------------------------------------------------- +// ------------------------- video timing analysis --------------------------- +// --------------------------------------------------------------------------- + +// System clock is 32Mhz. Slowest hsync is 15Khz/64us. Hcounter must thus be +// able to run to 2048 +reg [10:0] hcnt; +reg [10:0] vcnt; + +reg [10:0] hs_high; +reg [10:0] hs_low; +wire hs_pol = hs_high < hs_low; +wire [10:0] dsp_width = hs_pol?hs_low:hs_high; + +reg [10:0] vs_high; +reg [10:0] vs_low; +wire vs_pol = vs_high < vs_low; +wire [10:0] dsp_height = vs_pol?vs_low:vs_high; + +reg hsD, vsD; +always @(negedge clk) begin + // check if hsync has changed + hsD <= hs; + if(hsD != hs) begin + if(hs) hs_low <= hcnt; + else hs_high <= hcnt; + hcnt <= 11'd0; + + if(hs == hs_pol) begin + // check if vsync has changed + vsD <= vs; + if(vsD != vs) begin + if(vs) vs_low <= vcnt; + else vs_high <= vcnt; + vcnt <= 11'd0; + end else + vcnt <= vcnt + 11'd1; + end + end else + hcnt <= hcnt + 11'd1; +end + // --------------------------------------------------------------------------- // -------------------------------- spi client ------------------------------- // --------------------------------------------------------------------------- @@ -59,7 +101,7 @@ reg [7:0] cmd; reg [4:0] cnt; reg [10:0] bcnt; -reg [7:0] osd_buffer [2047:0]; // the OSD buffer itself +reg [7:0] buffer [2047:0]; // the OSD buffer itself // the OSD has its own SPI interface to the io controller always@(posedge sck, posedge ss) begin @@ -83,44 +125,51 @@ always@(posedge sck, posedge ss) begin // command 0x40: OSDCMDENABLE, OSDCMDDISABLE if(sbuf[6:3] == 4'b0100) - osd_enable <= sdi; + enabled <= sdi; end // command 0x20: OSDCMDWRITE if((cmd[7:3] == 5'b00100) && (cnt == 15)) begin - osd_buffer[bcnt] <= {sbuf[6:0], sdi}; + buffer[bcnt] <= {sbuf[6:0], sdi}; bcnt <= bcnt + 11'd1; end end end -localparam OSD_WIDTH = 10'd256; -localparam OSD_HEIGHT = 10'd128; // pixels are doubled vertically +// --------------------------------------------------------------------------- +// ------------------------------- OSD position ------------------------------ +// --------------------------------------------------------------------------- -localparam OSD_POS_X = (10'd640 - OSD_WIDTH)>>1; -localparam OSD_POS_Y = (10'd400 - OSD_HEIGHT)>>1; +wire expand_x = ((dsp_width > 1000)&&(dsp_height < 1000))?1:0; +wire expand_y = (dsp_height > 400)?1:0; + +wire [10:0] width = expand_x?10'd512:10'd256; +wire [10:0] height = expand_y?10'd128:10'd64; -localparam OSD_BORDER = 10'd2; +wire [10:0] border_x = expand_x?10'd4:10'd4; +wire [10:0] border_y = expand_y?10'd4:10'd2; -wire oe = osd_enable && ( - (hcnt >= OSD_POS_X-OSD_BORDER) && - (hcnt < (OSD_POS_X + OSD_WIDTH + OSD_BORDER)) && - (vcnt >= OSD_POS_Y - OSD_BORDER) && - (vcnt < (OSD_POS_Y + OSD_HEIGHT + OSD_BORDER))); +wire [10:0] pos_x = (dsp_width - width)>>1; +wire [10:0] pos_y = (dsp_height - height)>>1; -wire osd_content_area = - (hcnt >= OSD_POS_X) && - (hcnt < (OSD_POS_X + OSD_WIDTH)) && - (vcnt >= OSD_POS_Y) && - (vcnt < (OSD_POS_Y + OSD_HEIGHT)); +wire oe = enabled && ( + (hcnt >= pos_x - border_x) && + (hcnt < (pos_x + width + border_x)) && + (vcnt >= pos_y - border_y) && + (vcnt < (pos_y + height + border_y))); -wire [7:0] osd_hcnt = hcnt - OSD_POS_X + 7'd1; // one pixel offset for osd_byte register -wire [6:0] osd_vcnt = vcnt - OSD_POS_Y; +wire content_area = + (hcnt >= pos_x) && (hcnt < (pos_x + width - 1)) && + (vcnt >= pos_y) && (vcnt < (pos_y + height - 1)); -wire osd_pixel = osd_content_area?osd_byte[osd_vcnt[3:1]]:1'b0; +// one pixel offset for delay by byte register +wire [7:0] ihcnt = (expand_x?((hcnt-pos_x)>>1):(hcnt-pos_x))+8'd1; +wire [6:0] ivcnt = expand_y?((vcnt-pos_y)>>1):(vcnt-pos_y); -reg [7:0] osd_byte; +wire pixel = content_area?buffer_byte[ivcnt[2:0]]:1'b0; + +reg [7:0] buffer_byte; always @(posedge clk) - osd_byte <= osd_buffer[{osd_vcnt[6:4], osd_hcnt}]; + buffer_byte <= buffer[{ivcnt[5:3], ihcnt}]; endmodule diff --git a/cores/mist/scandoubler.v b/cores/mist/scandoubler.v new file mode 100644 index 0000000..da97113 --- /dev/null +++ b/cores/mist/scandoubler.v @@ -0,0 +1,167 @@ +// +// scandoubler.v +// +// Copyright (c) 2015 Till Harbaum +// +// 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 . + +// TODO: Delay vsync one line + +module scandoubler ( + // system interface + input clk, // 31.875 MHz + input clk_16, // from shifter + + // scanlines (00-none 01-25% 10-50% 11-75%) + input [1:0] scanlines, + + // shifter video interface + input hs_in, + input vs_in, + input [3:0] r_in, + input [3:0] g_in, + input [3:0] b_in, + + // output interface + output reg hs_out, + output reg vs_out, + output reg [3:0] r_out, + output reg [3:0] g_out, + output reg [3:0] b_out, + + output is15k +); + +// --------------------- create output signals ----------------- +// latch everything once more to make it glitch free and apply scanline effect +reg scanline; +always @(posedge clk) begin + hs_out <= hs_sd; + vs_out <= vs_in; + + // reset scanlines at every new screen + if(vs_out != vs_in) + scanline <= 1'b0; + + // toggle scanlines at begin of every hsync + if(hs_out && !hs_sd) + scanline <= !scanline; + + // if no scanlines or not a scanline + if(!scanline || scanlines == 2'b00) begin + r_out <= sd_out[11:8]; + g_out <= sd_out[7:4]; + b_out <= sd_out[3:0]; + end else begin + case(scanlines) + 2'b01: begin // reduce 25% = 1/2 + 1/4 + r_out <= { 1'b0, sd_out[11:9] } + { 2'b00, sd_out[11:10] }; + g_out <= { 1'b0, sd_out[7:5] } + { 2'b00, sd_out[7:6] }; + b_out <= { 1'b0, sd_out[3:1] } + { 2'b00, sd_out[3:2] }; + end + + 2'b10: begin // reduce 50% = 1/2 + r_out <= { 1'b0, sd_out[11:9] }; + g_out <= { 1'b0, sd_out[7:5] }; + b_out <= { 1'b0, sd_out[3:1] }; + end + + 2'b11: begin // reduce 75% = 1/4 + r_out <= { 2'b00, sd_out[11:10] }; + g_out <= { 2'b00, sd_out[7:6] }; + b_out <= { 2'b00, sd_out[3:2] }; + end + endcase + end // else: !if(!scanline || scanlines == 2'b00) +end + +// scan doubler output register +reg [11:0] sd_out; + +// ================================================================== +// ======================== the line buffers ======================== +// ================================================================== + +// 2 lines of 1024 pixels 3*4 bit RGB +reg [11:0] sd_buffer [2047:0]; + +// use alternating sd_buffers when storing/reading data +reg vsD; +reg line_toggle; +always @(negedge clk_16) begin + vsD <= vs_in; + + if(vsD != vs_in) + line_toggle <= 1'b0; + + // begin of incoming hsync + if(hsD && !hs_in) + line_toggle <= !line_toggle; +end + +always @(negedge clk_16) + sd_buffer[{line_toggle, hcnt}] <= { r_in, g_in, b_in }; + +// ================================================================== +// =================== horizontal timing analysis =================== +// ================================================================== + +// signal detection of 15khz if hsync frequency is less than 20KHz +assign is15k = hs_max > (16000000/20000); + +// total hsync time (in 16MHz cycles), hs_total reaches 1024 +reg [9:0] hs_max; +reg [9:0] hs_rise; +reg [9:0] hcnt; +reg hsD; + +always @(negedge clk_16) begin + hsD <= hs_in; + + // falling edge of hsync indicates start of line + if(hsD && !hs_in) begin + hs_max <= hcnt; + hcnt <= 10'd0; + end else + hcnt <= hcnt + 10'd1; + + // save position of rising edge + if(!hsD && hs_in) + hs_rise <= hcnt; +end + +// ================================================================== +// ==================== output timing generation ==================== +// ================================================================== + +reg [9:0] sd_hcnt; +reg hs_sd; + +// timing generation runs 32 MHz (twice the input signal analysis speed) +always @(posedge clk) begin + + // output counter synchronous to input and at twice the rate + sd_hcnt <= sd_hcnt + 10'd1; + if(hsD && !hs_in) sd_hcnt <= hs_max; + if(sd_hcnt == hs_max) sd_hcnt <= 10'd0; + + // replicate horizontal sync at twice the speed + if(sd_hcnt == hs_max) hs_sd <= 1'b0; + if(sd_hcnt == hs_rise) hs_sd <= 1'b1; + + // read data from line sd_buffer + sd_out <= sd_buffer[{~line_toggle, sd_hcnt}]; +end + +endmodule diff --git a/cores/mist/shifter.v b/cores/mist/shifter.v new file mode 100644 index 0000000..ffcbbef --- /dev/null +++ b/cores/mist/shifter.v @@ -0,0 +1,704 @@ +// +// shifter.v +// +// Atari ST(E) shifter implementation for the MiST board +// http://code.google.com/p/mist-board/ +// +// Copyright (c) 2013-2015 Till Harbaum +// +// 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 . + +module shifter ( + // system interface + input clk, // 31.875 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; + +always @(posedge clk) begin + st_hs <= h_sync; + + // hsync irq is generated after the rightmost border pixel column has been displayed + + // hsync starts at begin of blanking phase +// if(hcnt == (t1_h_blank_right)) +// st_hs <= 1'b1; + + // hsync ends at begin of left border +// if(hcnt == (t4_h_border_left - 10'd16)) +// st_hs <= 1'b0; + + // vsync irq is generated right after the last border line has been displayed + + // v_event is the begin of hsync. The hatari video.h says vbi happens 64 clock cycles + // ST hor counter runs at 16Mhz, thus the trigger is 128 events after h_sync + if(hcnt == v_event) begin + // vsync starts at begin of blanking phase + if(vcnt == t7_v_blank_bot + 10'd4) st_vs <= 1'b1; + + // vsync ends at begin of top border + if(vcnt == t10_v_border_top + 10'd0) 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); + +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 writing zero (60hz) + // within line 200. But this is sufficient for our detection + + // trigger in line 198/199 + if((vcnt[8:1] == 8'd97)||(vcnt[8:1] == 8'd98)||(vcnt[8:1] == 8'd99)|| + (vcnt[8:1] == 8'd100)||(vcnt[8:1] == 8'd101)) begin + // syncmode has changed from 1 to 0 (50 to 60 hz) + if((syncmode[1] == 1'b0) && (last_syncmode == 1'b1)) + bottom_overscan_cnt <= 4'd15; + end + + // trigger in line 284/285 + if((vcnt[8:1] == 8'd133)||(vcnt[8:1] == 8'd134)||(vcnt[8:1] == 8'd135)|| + (vcnt[8:1] == 8'd136)||(vcnt[8:1] == 8'd137)||(vcnt[8:1] == 8'd138)) begin + // syncmode has changed from 1 to 0 (50 to 60 hz) + if((syncmode[1] == 1'b0) && (last_syncmode == 1'b1)) + top_overscan_cnt <= 4'd15; + end + 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 + + // make sure each line starts with plane 0 + if(hcnt == de_h_start) + plane <= 2'd0; + + // 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 + if((hcnt == t3_h_blank_left) && (vcnt == (t7_v_blank_bot+10'd1))) begin + vaddr <= _v_bas_ad; + + // 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 + + vaddr <= vaddr + 23'd1; + end + + // advance plane counter + if(planes != 1) begin + plane <= plane + 2'd1; + if(plane == planes - 2'd1) + plane <= 2'd0; + end + end + 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 diff --git a/cores/mist/sync_adjust.v b/cores/mist/sync_adjust.v new file mode 100644 index 0000000..db6441e --- /dev/null +++ b/cores/mist/sync_adjust.v @@ -0,0 +1,136 @@ +// +// sync_adjust.v +// +// Ajust the video sync position to allow the user to center the +// video on screen +// +// Copyright (c) 2015 Till Harbaum +// +// 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 . + +module sync_adjust ( + // system interface + input clk, // 31.875 MHz + + input [15:0] adjust, + + input hs_in, + input vs_in, + + output reg hs_out, + output reg vs_out +); + +// This has to cope with 15kHz (64us). At 32MHz a counter will count +// from 0 to 2047 in that time. Thus a 11 bit counter is required + +// Extract and sign extend adjust values +wire signed [10:0] adjust_x = { {3{ adjust[15] }}, adjust[15:8] }; +wire signed [9:0] adjust_y = { {2{ adjust[7] }}, adjust[7:0] }; + +// ================================================================== +// ====================== input timing analysis ===================== +// ================================================================== + +// total hsync time (in 32MHz cycles), hs_total reaches 2048 +reg [10:0] hcnt; +reg hsD, vsD; + +// hsync rise at hcnt == 0, signals relative to this: +reg [10:0] hs_rise; +reg [10:0] hs_fall; +reg [10:0] v_event; + +// an event ecactly half a line delayed to v_event to generate +// vcntD which is stable over any change in v_event timinig +wire [10:0] hs_max_2 = { 1'b0, hs_max[10:1] }; +wire [10:0] v_event_2 = (v_event > hs_max_2)?v_event-hs_max_2:v_event+hs_max_2; + +reg [9:0] vcnt; +reg [9:0] vcntD; // delayed by half a line +reg [9:0] vs_rise; +reg [9:0] vs_fall; + +// since the counter is restarted at the falling edge, hs_fall contains +// the max counter value (total times - 1) +wire [10:0] hs_max = hs_fall; +wire [9:0] vs_max = vs_fall; + +always @(negedge clk) begin + hsD <= hs_in; + vsD <= vs_in; + + // hsync has changed + hcnt <= hcnt + 11'd1; + if(hsD != hs_in) begin + if(!hs_in) begin + hcnt <= 11'd0; + hs_fall <= hcnt; + end else + hs_rise <= hcnt; + end + + if(hcnt == v_event) + vcnt <= vcnt + 10'd1; + + // vsync has changed + if(vsD != vs_in) begin + if(!vs_in) begin + v_event <= hcnt; + vcnt <= 10'd0; + vs_fall <= vcnt; + end else + vs_rise <= vcnt; + end + + if(hcnt == v_event_2) + vcntD <= vcnt; +end + +// ================================================================== +// ==================== output timing generation ==================== +// ================================================================== + +wire [10:0] hcnt_out_rst = (adjust_x > 0)?(adjust_x-10'd1):(hs_max+adjust_x); +reg [10:0] hcnt_out; + +wire [9:0] vcnt_out_rst = (adjust_y > 0)?(adjust_y-9'd1):(vs_max+adjust_y); +reg [9:0] vcnt_out; + +always @(posedge clk) begin + // generate new hcnt with offset + if(hcnt == hcnt_out_rst) + hcnt_out <= 11'd0; + else + hcnt_out <= hcnt_out + 11'd1; + + // generate delayed hsync + if(hcnt_out == hs_rise) hs_out <= 1'b1; + if(hcnt_out == hs_fall) hs_out <= 1'b0; + + // generate delayed vsync timing + if(hcnt_out == v_event) begin + + if(vcntD == vcnt_out_rst) + vcnt_out <= 10'd0; + else + vcnt_out <= vcnt_out + 10'd1; + + // generate delayed vsync + if(vcnt_out == vs_rise) vs_out <= 1'b1; + if(vcnt_out == vs_fall) vs_out <= 1'b0; + end +end + +endmodule diff --git a/cores/mist/user_io.v b/cores/mist/user_io.v index b681c9c..9539f51 100644 --- a/cores/mist/user_io.v +++ b/cores/mist/user_io.v @@ -54,7 +54,8 @@ module user_io( // on-board buttons and dip switches output [1:0] BUTTONS, - output [1:0] SWITCHES + output [1:0] SWITCHES, + output scandoubler_disable ); // filter spi clock. the 8 bit gate delay is ~2.5ns in total @@ -65,13 +66,14 @@ reg [1:0] byte_cnt; reg [6:0] sbuf; reg [7:0] cmd; reg [3:0] bit_cnt; // 0..15 -reg [3:0] but_sw; +reg [4:0] but_sw; // counter runs 0..7,8..15,8..15,8..15 wire [2:0] tx_bit = ~(bit_cnt[2:0]); assign BUTTONS = but_sw[1:0]; assign SWITCHES = but_sw[3:2]; +assign scandoubler_disable = but_sw[4]; always@(negedge spi_sck) begin if(bit_cnt <= 7) @@ -189,10 +191,8 @@ always@(negedge spi_sck) begin if(bit_cnt == 15) begin eth_mac_begin <= 1'b0; - if(cmd == 1) begin - but_sw[3:1] <= sbuf[2:0]; - but_sw[0] <= SPI_MOSI; - end + if(cmd == 1) + but_sw <= { sbuf[3:0], SPI_MOSI }; // send ikbd byte to acia if(cmd == 2) begin diff --git a/cores/mist/video.v b/cores/mist/video.v index a510fc6..551ffba 100644 --- a/cores/mist/video.v +++ b/cores/mist/video.v @@ -1,11 +1,7 @@ // // video.v // -// Atari ST(E) shifter implementation for the MiST board -// http://code.google.com/p/mist-board/ -// -// Copyright (c) 2013 Till Harbaum -// Modified by Juan Carlos González Amestoy. +// Copyright (c) 2015 Till Harbaum // // 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 @@ -22,898 +18,247 @@ module video ( // system interface - input clk, // 31.875 MHz - input [1:0] bus_cycle, // bus-cycle for sync + input clk_128, // 127.5 MHz + input clk_32, // 31.875 MHz + input [1:0] bus_cycle, // bus-cycle for sync // SPI interface for OSD - input sck, - input ss, - input sdi, + input sck, + input ss, + input sdi, // memory interface - output reg [22:0] vaddr, // video word address counter - output read, // video read cycle - input [63:0] data, // video data read + output [22:0] vaddr, // video word address counter + output read, // video read cycle + input [63:0] data, // video data read // cpu register interface - input reg_clk, - input reg_reset, - input [15:0] reg_din, - input reg_sel, - input [5:0] reg_addr, - input reg_uds, - input reg_lds, - input reg_rw, - output reg [15:0] reg_dout, + 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 [15:0] cpu_dout, // screen interface - output reg hs, // H_SYNC - output reg vs, // V_SYNC - output reg [5:0] video_r, // Red[5:0] - output reg [5:0] video_g, // Green[5:0] - output reg [5:0] video_b, // Blue[5:0] + output hs, // H_SYNC + output vs, // V_SYNC + output [5:0] video_r, // Red[5:0] + output [5:0] video_g, // Green[5:0] + output [5:0] video_b, // Blue[5:0] // system config - input pal56, // use VGA compatible 56hz for PAL - input [1:0] scanlines, // scanlines (00-none 01-25% 10-50% 11-100%) - input [15:0] adjust, // hor/ver video adjust - input ste, // enable STE featurss - output osd_enable, + input viking_enable, // enable viking video card + input viking_himem, // let viking use memory from $e80000 + input scandoubler_disable, // don't use scandoubler in 15khz modes + input pal56, // use VGA compatible 56hz for PAL + input [1:0] scanlines, // scanlines (00-none 01-25% 10-50% 11-100%) + input [15:0] adjust, // hor/ver video adjust + input ste, // enable STE featurss // signals not affected by scan doubler for internal use like irqs - output st_de, - output reg st_vs, - output reg st_hs + output st_de, + output st_vs, + output st_hs ); -localparam LINE_WIDTH = 10'd640; -localparam LINE_BORDER = 10'd80; // width of left and right screen border +// give viking access to the memory if it's enabled +assign vaddr = viking_enable?viking_vaddr:shifter_vaddr; +assign read = viking_enable?viking_read:shifter_read; -localparam STATE_SYNC = 2'd0; -localparam STATE_BLANK = 2'd1; -localparam STATE_BORDER = 2'd2; -localparam STATE_DISP = 2'd3; +// if we use 15khz signals without scan doubler then we need +// to create a composite sync on hsync +wire enable_csync = sd_15khz_detected && scandoubler_disable; +wire csync = shifter_hs == shifter_vs; +assign hs = enable_csync?csync:stvid_hs; +assign vs = enable_csync?1'b1:stvid_vs; -// --------------------------------------------------------------------------- -// --------------------------- internal state counter ------------------------ -// --------------------------------------------------------------------------- +// ------------------------- OSD --------------------------- -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) && ( reg_clk == 0)) || - ((t == 2'd0) && ( reg_clk == 1)) || - ((t != 2'd3) && (t != 2'd0))) - t <= t + 2'd1; -end +// in viking mode OSD is operated at 64 MHz pixel clock +reg clk_64; +always @(posedge clk_128) + clk_64 <= !clk_64; -// 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; - -always @(posedge clk) begin - - // hsync irq is generated after the rightmost border pixel column has been displayed - - // Run st timing at full speed if no scan doubler is being used. Otherwise run - // it at half speed - if((!scan_doubler_enable) || vga_hcnt[0]) begin - - // hsync starts at begin of blanking phase - if(st_hcnt == (t1_h_blank_right - memory_prefetch)) - st_hs <= 1'b1; - - // hsync ends at begin of left border - if(st_hcnt == (t4_h_border_left - memory_prefetch)) - st_hs <= 1'b0; - end - - // vsync irq is generated right after the last border line has been displayed - - // v_event is the begin of hsync. The hatari video.h says vbi happens 64 clock cycles - // ST hor counter runs at 16Mhz, thus the trigger is 128 events after h_sync - if(st_h_active && (st_hcnt == (v_event))) begin - // vsync starts at begin of blanking phase - if(vcnt == t7_v_blank_bot - de_v_offset + 10'd4) st_vs <= 1'b1; - - // vsync ends at begin of top border - if(vcnt == t10_v_border_top - de_v_offset + 10'd0) 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 -wire h_sync_pol = config_string[121]; -wire [9:0] t0_h_border_right = config_string[120:111]; -wire [9:0] t1_h_blank_right = config_string[110:101]; -wire [9:0] t2_h_sync = config_string[100:91]; -wire [9:0] t3_h_blank_left = config_string[90:81]; -wire [9:0] t4_h_border_left = config_string[80:71]; -wire [9:0] t5_h_end = config_string[70:61]; - -wire v_sync_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 [2:0] shmode; -wire ttmid = (shmode == 3'd4); -wire mono = (shmode == 3'd2); -wire mid = (shmode == 3'd1); -wire low = (shmode == 3'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 - -wire mistvid = 1'b1; // enable MiST extended video mode(s) +wire osd_clk = viking_enable?clk_128:clk_32; -// --------------------------------------------------------------------------- -// ----------------------------- CPU register read --------------------------- -// --------------------------------------------------------------------------- - -always @(reg_sel, reg_rw, reg_uds, reg_lds, reg_addr, _v_bas_ad, shmode, vaddr, - syncmode, line_offset, pixel_offset, ste) begin - reg_dout = 16'h0000; - - // read registers - if(reg_sel && reg_rw) begin - - // video base register (r/w) - if(reg_addr == 6'h00) reg_dout <= { 8'h00, _v_bas_ad[22:15] }; - if(reg_addr == 6'h01) reg_dout <= { 8'h00, _v_bas_ad[14: 7] }; - if(ste && reg_addr == 6'h06) reg_dout <= { 8'h00, _v_bas_ad[ 6: 0], 1'b0 }; - - // video address counter (ro on ST) - if(reg_addr == 6'h02) reg_dout <= { 8'h00, vaddr[22:15] }; - if(reg_addr == 6'h03) reg_dout <= { 8'h00, vaddr[14:7 ] }; - if(reg_addr == 6'h04) reg_dout <= { 8'h00, vaddr[6:0], 1'b0 }; - - // syncmode register - if(reg_addr == 6'h05) reg_dout <= { 6'h00, syncmode, 8'h00 }; - - if(ste) begin - if(reg_addr == 6'h07) reg_dout <= { 8'h00, line_offset }; - if(reg_addr == 6'h32) reg_dout <= { 12'h000, pixel_offset }; - end - - // the color palette registers - if(reg_addr >= 6'h20 && reg_addr < 6'h30 ) begin - reg_dout[3:0] <= palette_b[reg_addr[3:0]]; - reg_dout[7:4] <= palette_g[reg_addr[3:0]]; - reg_dout[11:8] <= palette_r[reg_addr[3:0]]; - - // return only the 3 msb in non-ste mode - if(!ste) begin - reg_dout[3] <= 1'b0; - reg_dout[7] <= 1'b0; - reg_dout[11] <= 1'b0; - end - end - - // shift mode register - if(reg_addr == 6'h30) reg_dout <= { 5'h00, shmode, 8'h00 }; - end -end - -// --------------------------------------------------------------------------- -// ----------------------------- CPU register write -------------------------- -// --------------------------------------------------------------------------- - -// STE video address write signal is evaluated inside memory engine -wire ste_vaddr_write = ste && reg_sel && !reg_rw && !reg_lds; - -always @(negedge reg_clk) begin - if(reg_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; - - if(DEFAULT_MODE == 0) begin - // TOS default palette, can be disabled after tests - palette_r[ 0] <= 4'b111; palette_g[ 0] <= 4'b111; palette_b[ 0] <= 4'b111; - palette_r[ 1] <= 4'b111; palette_g[ 1] <= 4'b000; palette_b[ 1] <= 4'b000; - palette_r[ 2] <= 4'b000; palette_g[ 2] <= 4'b111; palette_b[ 2] <= 4'b000; - palette_r[ 3] <= 4'b111; palette_g[ 3] <= 4'b111; palette_b[ 3] <= 4'b000; - palette_r[ 4] <= 4'b000; palette_g[ 4] <= 4'b000; palette_b[ 4] <= 4'b111; - palette_r[ 5] <= 4'b111; palette_g[ 5] <= 4'b000; palette_b[ 5] <= 4'b111; - palette_r[ 6] <= 4'b000; palette_g[ 6] <= 4'b111; palette_b[ 6] <= 4'b111; - palette_r[ 7] <= 4'b101; palette_g[ 7] <= 4'b101; palette_b[ 7] <= 4'b101; - palette_r[ 8] <= 4'b011; palette_g[ 8] <= 4'b011; palette_b[ 8] <= 4'b011; - palette_r[ 9] <= 4'b111; palette_g[ 9] <= 4'b011; palette_b[ 9] <= 4'b011; - palette_r[10] <= 4'b011; palette_g[10] <= 4'b111; palette_b[10] <= 4'b011; - palette_r[11] <= 4'b111; palette_g[11] <= 4'b111; palette_b[11] <= 4'b011; - palette_r[12] <= 4'b011; palette_g[12] <= 4'b011; palette_b[12] <= 4'b111; - palette_r[13] <= 4'b111; palette_g[13] <= 4'b011; palette_b[13] <= 4'b111; - palette_r[14] <= 4'b011; palette_g[14] <= 4'b111; palette_b[14] <= 4'b111; - palette_r[15] <= 4'b000; palette_g[15] <= 4'b000; palette_b[15] <= 4'b000; - end else - palette_b[ 0] <= 4'b111; - - end else begin - // write registers - if(reg_sel && !reg_rw) begin - if(!reg_lds) begin - - // video base address hi/mid (ST and STE) - if(reg_addr == 6'h00) _v_bas_ad[22:15] <= reg_din[7:0]; - if(reg_addr == 6'h01) _v_bas_ad[14:7] <= reg_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(reg_addr[5:1] == 5'h00) _v_bas_ad[6:0] <= 7'h00; - - // the low byte can only be written in STE mode - if(ste && reg_addr == 6'h06) _v_bas_ad[6:0] <= reg_din[7:1]; - end - - // writing to sync mode toggles between 50 and 60 hz modes - if(reg_addr == 6'h05 && !reg_uds) syncmode <= reg_din[9:8]; - - // writing special STE registers - if(ste && !reg_lds) begin - if(reg_addr == 6'h07) line_offset <= reg_din[7:0]; - if(reg_addr == 6'h32) begin - pixel_offset <= reg_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 && !reg_uds && reg_lds) begin - if((reg_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(reg_addr >= 6'h20 && reg_addr < 6'h30 ) begin - if(!reg_uds) begin - if(!ste) palette_r[reg_addr[3:0]] <= { 1'b0 , reg_din[10:8] }; - else palette_r[reg_addr[3:0]] <= reg_din[11:8]; - end - - if(!reg_lds) begin - if(!ste) begin - palette_g[reg_addr[3:0]] <= { 1'b0, reg_din[6:4] }; - palette_b[reg_addr[3:0]] <= { 1'b0, reg_din[2:0] }; - end else begin - palette_g[reg_addr[3:0]] <= reg_din[7:4]; - palette_b[reg_addr[3:0]] <= reg_din[3:0]; - end - end - end - - // make msb writeable if MiST video modes are enabled - if(reg_addr == 6'h30 && !reg_uds) shmode <= { mistvid?reg_din[10]:1'b0, reg_din[9:8] }; - end - end -end - -// --------------------------------------------------------------------------- -// -------------------------- video signal generator ------------------------- -// --------------------------------------------------------------------------- - -// final st video data combined with OSD -wire [5:0] st_and_osd_r, st_and_osd_g, st_and_osd_b; - +// include OSD overlay osd osd ( - // OSD spi interface to io controller - .sdi (sdi ), - .sck (sck ), - .ss (ss ), - - .osd_enable (osd_enable ), + .clk ( osd_clk ), - // feed ST video signal into OSD - .clk (clk ), - .hcnt (vga_hcnt ), - .vcnt (vcnt ), - .in_r ({stvid_r, 2'b00}), - .in_g ({stvid_g, 2'b00}), - .in_b ({stvid_b, 2'b00}), + // OSD spi interface to io controller + .sdi ( sdi ), + .sck ( sck ), + .ss ( ss ), - // receive signal with OSD overlayed - .out_r (st_and_osd_r), - .out_g (st_and_osd_g), - .out_b (st_and_osd_b) -); - -// ----------------------- 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 -reg [11:0] color; -wire [3:0] color_r = { color[10:8], color[11] }; -wire [3:0] color_g = { color[ 6:4], color[ 7] }; -wire [3:0] color_b = { color[ 2:0], color[ 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; - -// this line is to be displayed darker in scanline mode -wire scanline = scan_doubler_enable && sd_vcnt[0]; - -// reading the scan doubler ram results in one extra delay and thus -// reading it has to look one vga_hcnt cycle into the future -wire [9:0] border_width = t5_h_end - t4_h_border_left; -wire [9:0] vga_hcnt_next = - (vga_hcnt == t5_h_end)?LINE_BORDER: - ((vga_hcnt >= t4_h_border_left)?(vga_hcnt-t4_h_border_left):(vga_hcnt+LINE_BORDER+10'd1)); - -always @(posedge clk) begin - hs <= h_sync_pol ^ ~vga_h_sync; - vs <= v_sync_pol ^ ~vga_v_sync; - - // color data is permanently read from the scan doubler buffers - color <= sd_buffer[{vga_hcnt_next, !sd_toggle}]; - - // drive video output and apply scanline effect if enabled - if(!scanline || scanlines == 2'b00) begin //if no scanlines or not a scanline - video_r <= blank?6'b000000:st_and_osd_r; - video_g <= blank?6'b000000:st_and_osd_g; - video_b <= blank?6'b000000:st_and_osd_b; - end else begin - case(scanlines) - 2'b01: begin //25% - video_r <= blank?6'b000000:(({1'b0,st_and_osd_r,1'b0}+{2'b00,st_and_osd_r})>>2); - video_g <= blank?6'b000000:(({1'b0,st_and_osd_g,1'b0}+{2'b00,st_and_osd_g})>>2); - video_b <= blank?6'b000000:(({1'b0,st_and_osd_b,1'b0}+{2'b00,st_and_osd_b})>>2); - end - - 2'b10: begin //50% - video_r <= blank?6'b000000:{1'b0,st_and_osd_r[5:1]}; - video_g <= blank?6'b000000:{1'b0,st_and_osd_g[5:1]}; - video_b <= blank?6'b000000:{1'b0,st_and_osd_b[5:1]}; - end - - 2'b11: begin //75% - video_r <= blank?6'b000000:{2'b00,st_and_osd_r[5:2]}; - video_g <= blank?6'b000000:{2'b00,st_and_osd_g[5:2]}; - video_b <= blank?6'b000000:{2'b00,st_and_osd_b[5:2]}; - end - endcase - end - - if(!scan_doubler_enable) begin - // hires mode: shift all planes and reload - // shift registers every 16 clocks - if(vga_hcnt[3:0] == 4'hf) 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[15:1] <= shift_0[14:0]; - shift_1[15:1] <= shift_1[14:0]; - shift_2[15:1] <= shift_2[14:0]; - shift_3[15:1] <= shift_3[14:0]; - end - 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); - -always @(posedge clk) begin - if(reg_reset) begin - top_overscan_cnt <= 4'd0; - bottom_overscan_cnt <= 4'd0; - end else begin - last_syncmode <= syncmode[1]; // delay syncmode to detect changes + // feed ST video signal into OSD + .hs ( stvid_hs ), + .vs ( stvid_vs ), - // this is the magic used to do "overscan". - // the magic actually involves more than writing zero (60hz) - // within line 200. But this is sufficient for our detection - - // trigger in line 198/199 - if((vcnt == { 8'd97, 2'b00} ) && (vga_hcnt == 10'd0) && (bottom_overscan_cnt != 0)) - bottom_overscan_cnt <= bottom_overscan_cnt - 4'd1; - - if((vcnt[9:2] == 8'd97)||(vcnt[9:2] == 8'd98)||(vcnt[9:2] == 8'd99)|| - (vcnt[9:2] == 8'd100)||(vcnt[9:2] == 8'd101)) begin - // syncmode has changed from 1 to 0 (50 to 60 hz) - if((syncmode[1] == 1'b0) && (last_syncmode == 1'b1)) - bottom_overscan_cnt <= 4'd15; - end - - // trigger in line 284/285 - if((vcnt == {8'd133, 2'b00 }) && (vga_hcnt == 10'd0) && (top_overscan_cnt != 0)) - top_overscan_cnt <= top_overscan_cnt - 4'd1; - - if((vcnt[9:2] == 8'd133)||(vcnt[9:2] == 8'd134)||(vcnt[9:2] == 8'd135)|| - (vcnt[9:2] == 8'd136)||(vcnt[9:2] == 8'd137)) begin - // syncmode has changed from 1 to 0 (50 to 60 hz) - if((syncmode[1] == 1'b0) && (last_syncmode == 1'b1)) - top_overscan_cnt <= 4'd15; - end - 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) + .r_in ( {stvid_r, 2'b00}), + .g_in ( {stvid_g, 2'b00}), + .b_in ( {stvid_b, 2'b00}), + + // receive signal with OSD overlayed + .r_out ( video_r ), + .g_out ( video_g ), + .b_out ( video_b ) ); -ste_shifter ste_shifter_2 ( - .skew (pixel_offset), - .in (ste_shift_2), - .out (ste_shifted_2) +// ------------- combine scandoubled shifter with viking ------------- +wire [3:0] stvid_r = viking_enable?viking_r:shifter_sd_r; +wire [3:0] stvid_g = viking_enable?viking_g:shifter_sd_g; +wire [3:0] stvid_b = viking_enable?viking_b:shifter_sd_b; +wire stvid_hs = viking_enable?viking_hs:vga_hs; +wire stvid_vs = viking_enable?viking_vs:vga_vs; + +// --------------- apply screen position adjustments ----------------- + +// apply vga sync polarity adjustment to scan doubler output. It doesn't hurt +// to do this even if 15khz modes are being used since the 15khz modes generate +// their csync signals from other signals +wire vga_hs = shifter_sd_adjusted_hs ^ vga_hs_pol; +wire vga_vs = shifter_sd_adjusted_vs ^ vga_vs_pol; + +wire shifter_sd_adjusted_hs; +wire shifter_sd_adjusted_vs; + +sync_adjust sync_adjust ( + .clk ( clk_32 ), + .adjust ( adjust ), + + .hs_in ( shifter_sd_hs ), + .vs_in ( shifter_sd_vs ), + + .hs_out ( shifter_sd_adjusted_hs ), + .vs_out ( shifter_sd_adjusted_vs ) +); + +// --------------- combine shifter with scan doubler ----------------- + +// use scandoubler if 15khz signal has been detected and +// scandoubler isn't disabled +wire use_scandoubler = sd_15khz_detected && !scandoubler_disable; + +// forward scandoubled signals whenever scandouble is to be used +wire [3:0] shifter_sd_r = use_scandoubler?sd_r:shifter_r; +wire [3:0] shifter_sd_g = use_scandoubler?sd_g:shifter_g; +wire [3:0] shifter_sd_b = use_scandoubler?sd_b:shifter_b; +wire shifter_sd_hs = use_scandoubler?sd_hs:shifter_hs; +wire shifter_sd_vs = use_scandoubler?sd_vs:shifter_vs; + +// --------------- the scan doubler for 15khz modes ----------------- +wire sd_15khz_detected; +wire sd_hs, sd_vs; +wire [3:0] sd_r, sd_g, sd_b; + +scandoubler scandoubler ( + .clk ( clk_32 ), // 31.875 MHz + .clk_16 ( clk_16 ), + + .scanlines ( scanlines ), + + // video input from shifter + .hs_in ( shifter_hs ), + .vs_in ( shifter_vs ), + .r_in ( shifter_r ), + .g_in ( shifter_g ), + .b_in ( shifter_b ), + + // output interface + .hs_out ( sd_hs ), + .vs_out ( sd_vs ), + .r_out ( sd_r ), + .g_out ( sd_g ), + .b_out ( sd_b ), + + .is15k ( sd_15khz_detected ) ); -ste_shifter ste_shifter_3 ( - .skew (pixel_offset), - .in (ste_shift_3), - .out (ste_shifted_3) -); +// --------------- the Atari ST(E) shifter chip ----------------- +wire shifter_hs, shifter_vs; +wire [3:0] shifter_r, shifter_g, shifter_b; -// move data into STE hard scroll shift registers -always @(posedge clk) begin - if((bus_cycle_L == 4'd14) && (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 +wire [22:0] shifter_vaddr; +wire shifter_read; -// --------------------------------------------------------------------------- -// ------------------------------- scan doubler ------------------------------ -// --------------------------------------------------------------------------- - -// scandoubler is used for the mid and low rez mode -wire scan_doubler_enable = mid || low; +// sync polarity to be used when outputting to VGA +wire vga_hs_pol, vga_vs_pol; + +// only use pal56 modes if the scandoubler is being used +wire use_pal56 = pal56 && !scandoubler_disable; -// scan doubler signale indicating first or second buffer used -wire sd_toggle = sd_vcnt[1]; +wire clk_16; + +shifter shifter ( + .clk ( clk_32 ), // 31.875 MHz + .bus_cycle ( bus_cycle ), // to sync memory access with cpu + + // memory interface + .vaddr ( shifter_vaddr ), // video word address + .read ( shifter_read ), // video read cycle + .data ( data ), // video data read + + // cpu register interface + .cpu_clk ( cpu_clk ), + .cpu_reset ( cpu_reset ), + .cpu_din ( cpu_din ), + .cpu_sel ( cpu_sel ), + .cpu_addr ( cpu_addr ), + .cpu_uds ( cpu_uds ), + .cpu_lds ( cpu_lds ), + .cpu_rw ( cpu_rw ), + .cpu_dout ( cpu_dout ), + + // screen interface + .hs ( shifter_hs ), // H_SYNC + .vs ( shifter_vs ), // V_SYNC + .video_r ( shifter_r ), // Red[5:0] + .video_g ( shifter_g ), // Green[5:0] + .video_b ( shifter_b ), // Blue[5:0] -// four scan doubler shift registers for up to 4 planes -reg [15:0] sd_shift_0, sd_shift_1, sd_shift_2, sd_shift_3; + // sync polarity to be used on vga + .vga_vs_pol ( vga_vs_pol ), + .vga_hs_pol ( vga_hs_pol ), + .clk_16 ( clk_16 ), -// msb of the shift registers is the index used to access the palette registers. -// Return border color index (0) if outside display area -wire [3:0] sd_index = (!de_v)?4'd0: - { sd_shift_3[15], sd_shift_2[15], sd_shift_1[15], sd_shift_0[15]}; - -// line buffer for two lines of 720 pixels (640 + 2 * 40 border) 3 * 4 (STE!) bit rgb data -reg [11:0] sd_buffer [(2*(LINE_WIDTH+2*LINE_BORDER))-1:0]; - -// the scan doubler needs to know which border (left or right) is currently being displayed -reg sd_border_side; - -// line counter used to create scan doubler states -reg [1:0] sd_vcnt; - -always @(posedge clk) begin - - // vertical state changes at end of hsync (begin of left blank) - if(vga_hcnt == v_event) begin - // reset state counter two vga lines before display start since scan doubler - // starts prefetching data two vga lines before - if(vcnt == (de_v_start-10'd2)) sd_vcnt <= 2'd0; - else sd_vcnt <= sd_vcnt + 2'd1; - end - - // permanently move data from data_latch into scan doublers shift registers - if((bus_cycle_L == 4'd15) && (plane == 2'd0)) begin - // normally data is directly moved from the input latches into the - // shift registers. Only on an ste with pixel scrolling enabled - // the data is moved through additional shift registers - if(!ste || (pixel_offset == 0) || ste_overscan_enable) begin - // load data into shift registers as required by color depth - sd_shift_0 <= data_latch[0]; - sd_shift_1 <= (planes > 3'd1)?data_latch[1]:16'h0000; - sd_shift_2 <= (planes > 3'd2)?data_latch[2]:16'h0000; - sd_shift_3 <= (planes > 3'd2)?data_latch[3]:16'h0000; - end else begin - sd_shift_0 <= ste_shifted_0; - sd_shift_1 <= ste_shifted_1; - sd_shift_2 <= ste_shifted_2; - sd_shift_3 <= ste_shifted_3; - end - - end else begin - // do the actual shifting - if((planes == 3'd1) || - ((planes == 3'd2) && (vga_hcnt[0] == 1'b1)) || - ((planes == 3'd4) && (vga_hcnt[1:0] == 2'b11))) begin - sd_shift_0[15:1] <= sd_shift_0[14:0]; - sd_shift_1[15:1] <= sd_shift_1[14:0]; - sd_shift_2[15:1] <= sd_shift_2[14:0]; - sd_shift_3[15:1] <= sd_shift_3[14:0]; - end - end - - // to store border colors we need to know which border we currently draw - if(st_hcnt == t4_h_border_left) sd_border_side <= 1'b0; - if(st_hcnt == t0_h_border_right) sd_border_side <= 1'b1; - - // scan doubler makes the st side operate at half the vga pixel clock - if(vga_hcnt[0] == 1'b0) begin - - // move data from shift register into line buffer. capture border colors as - // well to have the scan doubler delay in the border colors as well just in - // case a program changes border colors dynamically (e.g. different colors - // for left and right or top and bottom borders) - if(st_h_state == STATE_DISP) begin - sd_buffer[{LINE_BORDER + st_hcnt[9:0], sd_toggle}] <= - {palette_r[sd_index], palette_g[sd_index], palette_b[sd_index]}; - end else if(st_h_state == STATE_BORDER) begin - // move bites from left/right border into appropriate places in the line buffer - if(!sd_border_side) - // left border - sd_buffer[{st_hcnt[9:0] - t4_h_border_left-10'd1, sd_toggle}] <= - {palette_r[0], palette_g[0], palette_b[0]}; - else - // right border - sd_buffer[{st_hcnt[9:0] + LINE_BORDER, sd_toggle}] <= - {palette_r[0], palette_g[0], palette_b[0]}; - end - 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 in 1, 2 or 4 planes (16, 32 or 64 cycles) -wire [9:0] memory_prefetch = scan_doubler_enable?{ 4'd0, planes, 3'd0 }:{ 3'd0, planes, 4'd0 }; -wire [9:0] ste_overscan = ste_overscan_enable?memory_prefetch: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))?memory_prefetch:10'd0; -wire [9:0] de_h_start = t5_h_end - memory_prefetch - ste_prefetch; -wire [9:0] de_h_end = t0_h_border_right - memory_prefetch + ste_overscan; - -// extra lines required by overscan -wire [9:0] de_v_top_extra = top_overscan?10'd58:10'd0; // 29 extra ST lines at top -wire [9:0] de_v_bot_extra = bottom_overscan?10'd76:10'd0; // 38 extra ST lines at bottom -// line offset required for scan doubler -wire [9:0] de_v_offset = scan_doubler_enable?10'd2:10'd0; - -// calculate lines in which active display starts end ends -wire [9:0] de_v_start = t11_v_end - de_v_offset - de_v_top_extra; -wire [9:0] de_v_end = t6_v_border_bot - de_v_offset + de_v_bot_extra; - -// with scan doubler being active, there are two main clock cycles per st hor counter -// st_h_active makes sure these events only trigger once -wire st_h_active = (!scan_doubler_enable || t[0]); - -// latch to store STE updated video address -always @(posedge clk) begin - - // line in which memory access is enabled - // in scan doubler mode two lines ahead of vertical display enable - if(vga_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 && st_h_active) begin - if(st_hcnt == de_h_start) de <= 1'b1; - if(st_hcnt == de_h_end) de <= 1'b0; - end + // system config + .pal56 ( use_pal56 ), // use VGA compatible 56hz for PAL + .ste ( ste ), // enable STE features - // make sure each line starts with plane 0 - if(st_hcnt == de_h_start) - plane <= 2'd0; - - // 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 - if((vga_hcnt == t3_h_blank_left) && (vcnt == (t7_v_blank_bot-de_v_offset+10'd1))) begin - vaddr <= _v_bas_ad; - - // 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 - if(ttmid) begin - // TT mid compatible part fetches all 4 planes at once - data_latch[0] <= data[15: 0]; - data_latch[1] <= data[31:16]; - data_latch[2] <= data[47:32]; - data_latch[3] <= data[63:48]; - - vaddr <= vaddr + 23'd4; - end else begin - - // 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 - - vaddr <= vaddr + 23'd1; - end - end - - // advance plane counter - if(planes != 1) begin - plane <= plane + 2'd1; - if(plane == planes - 2'd1) - plane <= 2'd0; - end - end - 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 && st_h_active && (st_hcnt == de_h_end)) - 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(reg_addr == 6'h02) vaddr[22:15] <= reg_din[7:0]; - if(reg_addr == 6'h03) vaddr[14: 7] <= reg_din[7:0]; - if(reg_addr == 6'h04) vaddr[ 6: 0] <= reg_din[7:1]; - end - end -end - -// --------------------------------------------------------------------------- -// ------------------------- video timing generator -------------------------- -// --------------------------------------------------------------------------- - -// Two horizontal timings are generated: a vga one and a st one. Both are identical -// without the scan doubler being used (mono mode), but in scan doubler mode the -// st timing has exactly half the pixel rate as the vga one and all times are exactly -// twice as long -reg [9:0] vga_hcnt; // horizontal pixel counter -reg [1:0] vga_h_state; // 0=sync, 1=blank, 2=border, 3=display - -reg [9:0] st_hcnt; // horizontal pixel counter -reg [1:0] st_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) || (vga_h_state == STATE_BLANK) || - (v_state == STATE_SYNC) || (vga_h_state == STATE_SYNC); - -// time in horizontal timing where vertical states change (at the begin of the sync phase) -wire [9:0] v_event = t2_h_sync; - -// extend adjust values to 10 bits -wire [9:0] adjust_v = { adjust[7], adjust[7], adjust[7:0] }; -wire [9:0] adjust_h = { adjust[15], adjust[15], adjust[15:8] }; -reg vga_v_sync, vga_h_sync; - -always @(posedge clk) begin - // ------------- horizontal VGA timing generation ------------- - - // sync horizontal counter with bus cycle counter so cpu and video stay synchronous - // even if horizotal counter is affected by resolution changes - // the scan doubler is a special case as the atari line timing then expands over two vga - // lines and may/must be asynchronous to the vga timing at the end of the first line - if(vga_hcnt == t5_h_end) begin - if((bus_cycle_L == 4'd15) || (scan_doubler_enable && sd_vcnt[0])) - vga_hcnt <= 10'd0; - end else - vga_hcnt <= vga_hcnt + 10'd1; - - // generate user adjustable vga sync signal - if( vga_hcnt == t2_h_sync - adjust_h ) vga_h_sync <= 1'b1; - if( vga_hcnt == t3_h_blank_left - adjust_h ) vga_h_sync <= 1'b0; - - // generate horizontal video signal states - if( vga_hcnt == t2_h_sync ) vga_h_state <= STATE_SYNC; - if((vga_hcnt == t0_h_border_right) || (vga_hcnt == t4_h_border_left)) vga_h_state <= STATE_BORDER; - if((vga_hcnt == t1_h_blank_right) || (vga_hcnt == t3_h_blank_left)) vga_h_state <= STATE_BLANK; - if( vga_hcnt == t5_h_end) vga_h_state <= STATE_DISP; - - // ------------- horizontal ST timing generation ------------- - // Run st timing at full speed if no scan doubler is being used. Otherwise run - // it at half speed - if((!scan_doubler_enable) || vga_hcnt[0]) begin - if(st_hcnt == t5_h_end) begin - // changing video modes toggles scan_doubler_enable and will bring - // the two hcnt counters out of sync. So we'll resync st_hcnt with vgs_hcnt here - if((vga_hcnt == t5_h_end) && (!scan_doubler_enable || !sd_vcnt[0])) - st_hcnt <= 10'd0; - end else - st_hcnt <= st_hcnt + 10'd1; - - // generate horizontal video signal states - if( st_hcnt == t2_h_sync ) st_h_state <= STATE_SYNC; - if((st_hcnt == t0_h_border_right + ste_overscan) || (st_hcnt == t4_h_border_left)) st_h_state <= STATE_BORDER; - if((st_hcnt == t1_h_blank_right) || (st_hcnt == t3_h_blank_left)) st_h_state <= STATE_BLANK; - if( st_hcnt == t5_h_end) st_h_state <= STATE_DISP; - end - - // vertical state changes at end of hsync (begin of left blank) - if(vga_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 - adjust_v ) vga_v_sync <= 1'b1; - if( vcnt == t9_v_blank_top - adjust_v ) vga_v_sync <= 1'b0; - - // generate vertical video signal states - if( vcnt == t8_v_sync ) v_state <= STATE_SYNC; - if((vcnt == t6_v_border_bot) || (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 == t11_v_end) 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 + // signals not affected by scan doubler for internal use like irqs + .st_de ( st_de ), + .st_vs ( st_vs ), + .st_hs ( st_hs ) ); -always @(skew, in) begin - out = 16'h0000; +// --------------- the Viking compatible 1280x1024 graphics card ----------------- +wire viking_hs, viking_vs; +wire [3:0] viking_r, viking_g, viking_b; - 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 +wire [22:0] viking_vaddr; +wire viking_read; + +viking viking ( + .pclk ( clk_128 ), // 128MHz + .himem ( viking_himem ), + .bclk ( cpu_clk ), + .bus_cycle ( bus_cycle ), // bus-cycle to sync video memory access with cpu + + // memory interface + .addr ( viking_vaddr ), // video word address + .read ( viking_read ), // video read cycle + .data ( data ), // video data read + + // video output + .hs ( viking_hs ), + .vs ( viking_vs ), + .r ( viking_r ), + .g ( viking_g ), + .b ( viking_b ) +); endmodule diff --git a/cores/mist/video_modes.v b/cores/mist/video_modes.v index 1d09c6b..5fdd0c3 100644 --- a/cores/mist/video_modes.v +++ b/cores/mist/video_modes.v @@ -19,10 +19,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// video docs: +// VGA video docs: // http://martin.hinner.info/vga/timing.html // http://www.epanorama.net/faq/vga2rgb/calc.html +// Atari video timings: +// http://www.atari-forum.com/viewtopic.php?f=16&t=24855&start=350 + // clocks on real sts: // PAL 32084988 Hz // NTSC 32042400 Hz @@ -39,6 +42,9 @@ // vbl at cycle counter 64 (64 cycles after hbl) +// All video modes are based on a 32MHz pixel clock. This is two times the mid rez pixel clock and +// four times the low rez pixel clock. + module video_modes ( inout mono, // select monochrome mode (and not color) input pal, // select pal mode (and not ntsc) if a color mode is selected @@ -51,9 +57,6 @@ module video_modes ( // ---------------------------- generic timing parameters -------------------- // --------------------------------------------------------------------------- -localparam H_ACT = 10'd640; -localparam V_ACT = 10'd400; - // TIMING CONSTRAINTS: // The total width (act+both blank+2*border+sync) must be a multiple of 16, for // scan doubled modes a multiple of 8 @@ -63,15 +66,16 @@ localparam V_ACT = 10'd400; // --------------------------------------------------------------------------- // 56Hz replacement for Atari 50Hz low and medium resolution video mode scan doubled: +// displayed 640x200 // total: 928x612, active incl border: 720x560, displayed: 640x400 // horizontal scan rate: 17.27 kHz ST, 34.48 kHz VGA, vertical scan rate: 56.34 hz wire [121:0] pal56_config_str; conf pal56_conf( - // front porch sync width back porch border width sync polarity - .h_fp ( 10'd44), .h_s (10'd120), .h_bp ( 10'd44), .h_bd (10'd40), .h_sp (1'b1), - .v_fp ( 10'd24), .v_s ( 10'd4), .v_bp ( 10'd24), .v_tb (10'd80), .v_bb (10'd80), .v_sp (1'b1), - .str (pal56_config_str) + // display front porch sync width back porch border width sync polarity + .h_ds(10'd640), .h_fp( 10'd44), .h_s(10'd120), .h_bp( 10'd44), .h_lb(10'd40), .h_rb(10'd40), .h_sp(1'b1), + .v_ds(10'd200), .v_fp( 10'd12), .v_s( 10'd2), .v_bp( 10'd12), .v_tb(10'd40), .v_bb(10'd40), .v_sp(1'b1), + .str (pal56_config_str) ); // --------------------------------------------------------------------------- @@ -79,16 +83,20 @@ conf pal56_conf( // --------------------------------------------------------------------------- // Atari 50Hz low and medium resolution video mode scan doubled: +// According to troed: 40/40/28/320/72/12 +// -> sync-80 bl-80 brd-56 dsp-640 brd-144 bl-24 +// displayed 640x200 // total: 1024x626, active incl border: 800x560, displayed: 640x400 -// horizontal scan rate: 15.625 kHz ST, 31.25 kHz VGA, vertical scan rate: 49.92 hz +// horizontal scan rate: 15.625 kHz ST (, 31.25 kHz VGA), vertical scan rate: 49.92 hz wire [121:0] pal50_config_str; conf pal50_conf( - // front porch sync width back porch border width sync polarity - .h_fp ( 10'd80), .h_s ( 10'd64), .h_bp ( 10'd80), .h_bd (10'd80), .h_sp (1'b1), -// .v_fp ( 10'd42), .v_s ( 10'd8), .v_bp ( 10'd42), .v_tb (10'd58), .v_bb (10'd76), .v_sp (1'b1), - .v_fp ( 10'd30), .v_s ( 10'd6), .v_bp ( 10'd30), .v_tb (10'd80), .v_bb (10'd80), .v_sp (1'b1), - .str (pal50_config_str) + // display front porch sync width back porch border width sync polarity +//.h_ds(10'd640), .h_fp( 10'd80), .h_s( 10'd64), .h_bp( 10'd80), .h_lb(10'd80), .h_rb(10'd80), .h_sp(1'b1), + .h_ds(10'd640), .h_fp( 10'd80), .h_s( 10'd80), .h_bp( 10'd24), .h_lb(10'd72), .h_rb(10'd128), .h_sp(1'b1), +// .h_ds(10'd640), .h_fp( 10'd80), .h_s( 10'd80), .h_bp( 10'd24), .h_lb(10'd56), .h_rb(10'd144), .h_sp(1'b1), + .v_ds(10'd200), .v_fp( 10'd15), .v_s( 10'd3), .v_bp( 10'd15), .v_tb(10'd40), .v_bb(10'd40), .v_sp(1'b1), + .str (pal50_config_str) ); // --------------------------------------------------------------------------- @@ -101,10 +109,10 @@ conf pal50_conf( wire [121:0] ntsc_config_str; conf ntsc_conf( - // front porch sync width back porch border width sync polarity - .h_fp ( 10'd76), .h_s ( 10'd64), .h_bp ( 10'd76), .h_bd (10'd80), .h_sp (1'b1), - .v_fp ( 10'd20), .v_s ( 10'd6), .v_bp ( 10'd20), .v_tb (10'd40), .v_bb (10'd40), .v_sp (1'b0), - .str (ntsc_config_str) + // display front porch sync width back porch border width sync polarity + .h_ds(10'd640), .h_fp( 10'd76), .h_s( 10'd64), .h_bp( 10'd76), .h_lb(10'd80), .h_rb(10'd80), .h_sp(1'b1), + .v_ds(10'd200), .v_fp( 10'd10), .v_s( 10'd3), .v_bp( 10'd10), .v_tb(10'd20), .v_bb(10'd20), .v_sp(1'b0), + .str (ntsc_config_str) ); // --------------------------------------------------------------------------- @@ -117,10 +125,10 @@ conf ntsc_conf( wire [121:0] mono_config_str; conf mono_conf( - // front porch sync width back porch border width sync polarity - .h_fp (10'd108), .h_s ( 10'd40), .h_bp (10'd108), .h_bd ( 10'd0), .h_sp (1'b0), - .v_fp ( 10'd48), .v_s ( 10'd5), .v_bp ( 10'd48), .v_tb ( 10'd0), .v_bb ( 10'd0), .v_sp (1'b0), - .str (mono_config_str) + // display front porch sync width back porch border width sync polarity + .h_ds( 10'd640), .h_fp(10'd108), .h_s( 10'd40), .h_bp(10'd108), .h_lb( 10'd0), .h_rb( 10'd0), .h_sp(1'b0), + .v_ds( 10'd400), .v_fp( 10'd48), .v_s( 10'd5), .v_bp( 10'd48), .v_tb( 10'd0), .v_bb( 10'd0), .v_sp(1'b0), + .str (mono_config_str) ); @@ -132,44 +140,44 @@ endmodule // --------------------------------------------------------------------------- // ------------------ video timing config string generator ------------------- // --------------------------------------------------------------------------- -module conf ( - input [9:0] h_fp, // horizontal front porch width - input [9:0] h_s, // horizontal sync width - input [9:0] h_bp, // horizontal back porch width - input [9:0] h_bd, // horizontal border width - input h_sp, // horizontal sync polarity - input [9:0] v_fp, // vertical front porch width - input [9:0] v_s, // vertical sync width - input [9:0] v_bp, // vertical back porch width - input [9:0] v_tb, // vertical border width top - input [9:0] v_bb, // vertical border width bottom - input v_sp, // vertical sync polarity +module conf ( + input [9:0] h_ds, // horizontal display + input [9:0] h_fp, // horizontal front porch width + input [9:0] h_s, // horizontal sync width + input [9:0] h_bp, // horizontal back porch width + input [9:0] h_lb, // horizontal left border width + input [9:0] h_rb, // horizontal right border width + input h_sp, // horizontal sync polarity + + input [9:0] v_ds, // vertical display + input [9:0] v_fp, // vertical front porch width + input [9:0] v_s, // vertical sync width + input [9:0] v_bp, // vertical back porch width + input [9:0] v_tb, // vertical top border width + input [9:0] v_bb, // vertical bottom border width + input v_sp, // vertical sync polarity output [121:0] str ); -// all Atari video mods are based on a 640x400 screen -localparam H_ACT = 10'd640; -localparam V_ACT = 10'd400; - // all parameters are assembled into one config string wire [60:0] h_str = { h_sp, - H_ACT - 10'd1, - H_ACT + h_bd - 10'd1, - H_ACT + h_bd + h_fp - 10'd1, - H_ACT + h_bd + h_fp + h_s - 10'd1, - H_ACT + h_bd + h_fp + h_s + h_bp - 10'd1, - H_ACT + h_bd + h_fp + h_s + h_bp + h_bd - 10'd1}; + h_ds - 10'd1, + h_ds + h_rb - 10'd1, + h_ds + h_rb + h_fp - 10'd1, + h_ds + h_rb + h_fp + h_s - 10'd1, + h_ds + h_rb + h_fp + h_s + h_bp - 10'd1, + h_ds + h_rb + h_fp + h_s + h_bp + h_lb - 10'd1}; wire [60:0] v_str = { v_sp, - V_ACT - 10'd1, - V_ACT + v_bb - 10'd1, - V_ACT + v_bb + v_fp - 10'd1, - V_ACT + v_bb + v_fp + v_s - 10'd1, - V_ACT + v_bb + v_fp + v_s + v_bp - 10'd1, - V_ACT + v_bb + v_fp + v_s + v_bp + v_tb - 10'd1}; + v_ds - 10'd1, + v_ds + v_bb - 10'd1, + v_ds + v_bb + v_fp - 10'd1, + v_ds + v_bb + v_fp + v_s - 10'd1, + v_ds + v_bb + v_fp + v_s + v_bp - 10'd1, + v_ds + v_bb + v_fp + v_s + v_bp + v_tb - 10'd1}; assign str = { h_str, v_str }; -endmodule \ No newline at end of file +endmodule diff --git a/cores/mist/viking.v b/cores/mist/viking.v index 092c93e..fb25232 100644 --- a/cores/mist/viking.v +++ b/cores/mist/viking.v @@ -4,7 +4,7 @@ // Atari ST(E) Viking/SM194 // http://code.google.com/p/mist-board/ // -// Copyright (c) 2013 Till Harbaum +// Copyright (c) 2013-2015 Till Harbaum // // 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 @@ -37,9 +37,9 @@ module viking ( // VGA output (multiplexed with sm124 output in top level) output hs, output vs, - output [5:0] r, - output [5:0] g, - output [5:0] b + output [3:0] r, + output [3:0] g, + output [3:0] b ); localparam BASE = 23'h600000; // c00000 @@ -98,8 +98,8 @@ reg[10:0] h_cnt; // 0..2047 assign hs = ((h_cnt >= HBP1+H+HFP) && (h_cnt < HBP1+H+HFP+HS))?0:1; always@(posedge pclk) begin if(h_cnt==HBP1+H+HFP+HS+HBP2-1) begin - // make sure a line starts with the "viking" bus cyle (2) - // shifter has cycle 0, cpu has cycles 1 and 3 + // make sure a line starts with the "video" bus cyle (0) + // cpu has cycles 1 and 3 if(bus_cycle_L == { 2'd1, 4'd15 }) h_cnt<=0; end else @@ -116,9 +116,6 @@ always@(posedge pclk) begin end end -// reorder words 1:2:3:4 -> 4:3:2:1 -wire [63:0] data_reorder = { data[15:0], data[31:16], data[47:32], data[63:48] }; - reg [63:0] input_latch; reg [63:0] shift_register; @@ -131,10 +128,13 @@ always@(posedge pclk) begin addr <= addr + 23'd4; // advance 4 words (64 bits) if(me && (bus_cycle_L == 6'h2f)) - input_latch <= data_reorder; + input_latch <= data; if(bus_cycle_L == 6'h3f) - shift_register <= input_latch; + // reorder words 1:2:3:4 -> 4:3:2:1 + shift_register <= + { input_latch[15:0], input_latch[31:16], + input_latch[47:32], input_latch[63:48] }; else shift_register[63:1] <= shift_register[62:0]; end @@ -146,10 +146,10 @@ wire de = (v_cnt < V)&&(h_cnt >= HBP1)&&(h_cnt < HBP1+H); wire pix = de?(!shift_register[63]):1'b0; -// drive all 18 rgb bits from the data bit -wire [5:0] pix6 = { pix, pix, pix, pix, pix, pix }; -assign r = pix6; -assign g = pix6; -assign b = pix6; +// drive all 12 rgb bits from the data bit +wire [3:0] pix4 = { pix, pix, pix, pix }; +assign r = pix4; +assign g = pix4; +assign b = pix4; endmodule