diff --git a/cores/mist/sync_adjust.v b/cores/mist/sync_adjust.v index db6441e..e3195a6 100644 --- a/cores/mist/sync_adjust.v +++ b/cores/mist/sync_adjust.v @@ -102,10 +102,10 @@ end // ==================== output timing generation ==================== // ================================================================== -wire [10:0] hcnt_out_rst = (adjust_x > 0)?(adjust_x-10'd1):(hs_max+adjust_x); +wire [10:0] hcnt_out_rst = (adjust_x < 0)?(10'd0-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); +wire [9:0] vcnt_out_rst = (adjust_y < 0)?(9'd0-adjust_y-9'd1):(vs_max-adjust_y); reg [9:0] vcnt_out; always @(posedge clk) begin diff --git a/cores/mist/video_modes.v b/cores/mist/video_modes.v index 5fdd0c3..caa0b85 100644 --- a/cores/mist/video_modes.v +++ b/cores/mist/video_modes.v @@ -92,8 +92,8 @@ conf pal56_conf( wire [121:0] pal50_config_str; conf pal50_conf( // 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'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) diff --git a/tests/verilator/video/Makefile b/tests/verilator/video/Makefile new file mode 100644 index 0000000..1293a76 --- /dev/null +++ b/tests/verilator/video/Makefile @@ -0,0 +1,26 @@ +PROJECT=video +NOWARN = -Wno-UNOPTFLAT -Wno-WIDTH -Wno-COMBDLY -Wno-CASEINCOMPLETE # --report-unoptflat # -Wno-UNOPTFLAT +SRC = osd.v scandoubler.v shifter.v video_modes.v viking.v sync_adjust.v $(PROJECT).v $(PROJECT)_tb.cpp + +all: $(PROJECT).vcd + +obj_dir/stamp: $(SRC) + verilator $(NOWARN) --cc --trace -CFLAGS `sdl-config --cflags` --exe $(PROJECT).v $(PROJECT)_tb.cpp -LDFLAGS "`sdl-config --libs`" + touch obj_dir/stamp + +obj_dir/V$(PROJECT): obj_dir/stamp + make -j -C obj_dir/ -f V$(PROJECT).mk V$(PROJECT) + +$(PROJECT).vcd: obj_dir/V$(PROJECT) + obj_dir/V$(PROJECT) + +clean: + rm -rf obj_dir + rm -f $(PROJECT).vcd + rm -f *~ + +check: + for i in *.v ; do cmp $$i ../../hdl/mist/$$i ; done + +view: $(PROJECT).vcd + gtkwave $< $(PROJECT).sav & diff --git a/tests/verilator/video/high.raw b/tests/verilator/video/high.raw new file mode 100644 index 0000000..140324a Binary files /dev/null and b/tests/verilator/video/high.raw differ diff --git a/tests/verilator/video/low.raw b/tests/verilator/video/low.raw new file mode 100644 index 0000000..ec18648 Binary files /dev/null and b/tests/verilator/video/low.raw differ diff --git a/tests/verilator/video/mid.raw b/tests/verilator/video/mid.raw new file mode 100644 index 0000000..06246bb Binary files /dev/null and b/tests/verilator/video/mid.raw differ diff --git a/tests/verilator/video/osd.v b/tests/verilator/video/osd.v new file mode 100644 index 0000000..a2ef648 --- /dev/null +++ b/tests/verilator/video/osd.v @@ -0,0 +1,175 @@ +// +// osd.v +// +// On Screen Display implementation for the MiST board +// http://code.google.com/p/mist-board/ +// +// 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 osd ( + input clk, // 31.875 MHz + + // SPI interface for OSD + input sck, + input ss, + input sdi, + + input hs, + input vs, + + 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] r_out, // Red[5:0] + output [5:0] g_out, // Green[5:0] + output [5:0] b_out // Blue[5:0] +); + +// 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 ------------------------------- +// --------------------------------------------------------------------------- + +// this core supports only the display related OSD commands +// of the minimig + +reg [7:0] sbuf; +reg [7:0] cmd; +reg [4:0] cnt; +reg [10:0] bcnt; + +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 + if(ss == 1'b1) begin + cnt <= 5'd0; + bcnt <= 11'd0; + end else begin + sbuf <= { sbuf[6:0], sdi}; + + // 0:7 is command, rest payload + if(cnt < 15) + cnt <= cnt + 4'd1; + else + cnt <= 4'd8; + + if(cnt == 7) begin + cmd <= {sbuf[6:0], sdi}; + + // lower three command bits are line address + bcnt <= { sbuf[1:0], sdi, 8'h00}; + + // command 0x40: OSDCMDENABLE, OSDCMDDISABLE + if(sbuf[6:3] == 4'b0100) + enabled <= sdi; + end + + // command 0x20: OSDCMDWRITE + if((cmd[7:3] == 5'b00100) && (cnt == 15)) begin + buffer[bcnt] <= {sbuf[6:0], sdi}; + bcnt <= bcnt + 11'd1; + end + end +end + +// --------------------------------------------------------------------------- +// ------------------------------- OSD position ------------------------------ +// --------------------------------------------------------------------------- + +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; + +wire [10:0] border_x = expand_x?10'd4:10'd4; +wire [10:0] border_y = expand_y?10'd4:10'd2; + +wire [10:0] pos_x = (dsp_width - width)>>1; +wire [10:0] pos_y = (dsp_height - height)>>1; + +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 content_area = + (hcnt >= pos_x) && (hcnt < (pos_x + width - 1)) && + (vcnt >= pos_y) && (vcnt < (pos_y + height - 1)); + +// 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); + +wire pixel = content_area?buffer_byte[ivcnt[2:0]]:1'b0; + +reg [7:0] buffer_byte; +always @(posedge clk) + buffer_byte <= buffer[{ivcnt[5:3], ihcnt}]; + +endmodule diff --git a/tests/verilator/video/readme.txt b/tests/verilator/video/readme.txt new file mode 100644 index 0000000..167db27 --- /dev/null +++ b/tests/verilator/video/readme.txt @@ -0,0 +1,31 @@ +video tests +----------- + +These tests run the entire Atari ST video subsystem through a verilator +simulation. The screen is simulated using a SDL window. + +In video_tb.cpp several things can be configured: + +DUMP -- enable signal dump. Slows down simulation +VIKING -- enable simulated viking video card +REZ -- shifter resolution LOW=0, MID=1, HI=2 +SD -- scan doubler on/off +SL -- scanlines 0=off -> 3=75% +PAL -- enable 1-PAL or 0-NTSC +PAL56 -- use 56Hz PAL video modes + +Different modes to be tested: +LOWREZ PAL50 without scan doubler +LOWREZ PAL50 with scan doubler +LOWREZ PAL56 without scan doubler +LOWREZ PAL56 with scan doubler +LOWREZ NTSC without scan doubler +LOWREZ NTSC with scan doubler +MIDREZ PAL50 without scan doubler +MIDREZ PAL50 with scan doubler +MIDREZ PAL56 without scan doubler +MIDREZ PAL56 with scan doubler +MIDREZ NTSC without scan doubler +MIDREZ NTSC with scan doubler +HIREZ +VIKING diff --git a/tests/verilator/video/scandoubler.v b/tests/verilator/video/scandoubler.v new file mode 100644 index 0000000..da97113 --- /dev/null +++ b/tests/verilator/video/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/tests/verilator/video/shifter.v b/tests/verilator/video/shifter.v new file mode 100644 index 0000000..ffcbbef --- /dev/null +++ b/tests/verilator/video/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/tests/verilator/video/sync_adjust.v b/tests/verilator/video/sync_adjust.v new file mode 100644 index 0000000..e3195a6 --- /dev/null +++ b/tests/verilator/video/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)?(10'd0-adjust_x-10'd1):(hs_max-adjust_x); +reg [10:0] hcnt_out; + +wire [9:0] vcnt_out_rst = (adjust_y < 0)?(9'd0-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/tests/verilator/video/video.sav b/tests/verilator/video/video.sav new file mode 100644 index 0000000..d28f38a --- /dev/null +++ b/tests/verilator/video/video.sav @@ -0,0 +1,52 @@ +[*] +[*] GTKWave Analyzer v3.3.58 (w)1999-2014 BSI +[*] Wed Feb 18 14:49:30 2015 +[*] +[dumpfile] "/home/tharbaum/tmp/mist/tools/verilator_video/video.vcd" +[dumpfile_mtime] "Wed Feb 18 14:46:53 2015" +[dumpfile_size] 153829409 +[savefile] "/home/tharbaum/tmp/mist/tools/verilator_video/video.sav" +[timestart] 14081095 +[size] 1134 700 +[pos] 19 117 +*-10.703690 14083937 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] TOP. +[treeopen] TOP.v. +[treeopen] TOP.v.shifter. +[sst_width] 202 +[signals_width] 206 +[sst_expanded] 1 +[sst_vpaned_height] 250 +@28 +TOP.v.clk_32 +@22 +TOP.v.vaddr[22:0] +@28 +TOP.v.shifter.h_state[1:0] +TOP.v.read +@22 +TOP.v.shifter.data_latch(0)[15:0] +TOP.v.shifter.data_latch(1)[15:0] +TOP.v.shifter.data_latch(2)[15:0] +TOP.v.shifter.data_latch(3)[15:0] +TOP.v.shifter.shift_0[15:0] +TOP.v.shifter.shift_1[15:0] +TOP.v.shifter.shift_2[15:0] +TOP.v.shifter.shift_3[15:0] +TOP.v.shifter_r[3:0] +TOP.v.shifter_g[3:0] +TOP.v.shifter_b[3:0] +@28 +TOP.v.shifter.pclk +@22 +TOP.v.shifter.bus_cycle_L[3:0] +@28 +TOP.v.shifter.t[1:0] +@24 +TOP.v.shifter.hcnt[9:0] +@29 +TOP.v.shifter.bus_cycle[1:0] +@28 +TOP.v.shifter.de +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/tests/verilator/video/video.v b/tests/verilator/video/video.v new file mode 100644 index 0000000..551ffba --- /dev/null +++ b/tests/verilator/video/video.v @@ -0,0 +1,264 @@ +// +// video.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 . + +module video ( + // system interface + 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, + + // memory interface + output [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 [15:0] cpu_dout, + + // screen interface + 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 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 st_vs, + output st_hs +); + +// 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; + +// 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; + +// ------------------------- OSD --------------------------- + +// in viking mode OSD is operated at 64 MHz pixel clock +reg clk_64; +always @(posedge clk_128) + clk_64 <= !clk_64; + +wire osd_clk = viking_enable?clk_128:clk_32; + +// include OSD overlay +osd osd ( + .clk ( osd_clk ), + + // OSD spi interface to io controller + .sdi ( sdi ), + .sck ( sck ), + .ss ( ss ), + + // feed ST video signal into OSD + .hs ( stvid_hs ), + .vs ( stvid_vs ), + + .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 ) +); + +// ------------- 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 ) +); + +// --------------- the Atari ST(E) shifter chip ----------------- +wire shifter_hs, shifter_vs; +wire [3:0] shifter_r, shifter_g, shifter_b; + +wire [22:0] shifter_vaddr; +wire shifter_read; + +// 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; + +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] + + // sync polarity to be used on vga + .vga_vs_pol ( vga_vs_pol ), + .vga_hs_pol ( vga_hs_pol ), + .clk_16 ( clk_16 ), + + // system config + .pal56 ( use_pal56 ), // use VGA compatible 56hz for PAL + .ste ( ste ), // enable STE features + + // signals not affected by scan doubler for internal use like irqs + .st_de ( st_de ), + .st_vs ( st_vs ), + .st_hs ( st_hs ) +); + +// --------------- the Viking compatible 1280x1024 graphics card ----------------- +wire viking_hs, viking_vs; +wire [3:0] viking_r, viking_g, viking_b; + +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/tests/verilator/video/video_modes.v b/tests/verilator/video/video_modes.v new file mode 100644 index 0000000..caa0b85 --- /dev/null +++ b/tests/verilator/video/video_modes.v @@ -0,0 +1,183 @@ +// +// video_modes.v +// +// Video modes for Atari ST shifter implementation for the MiST board +// http://code.google.com/p/mist-board/ +// +// Copyright (c) 2013 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 . + +// 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 +// MIST 31875000 Hz + +// real ST timing + +// Starting with VBI +// Atari Timing as hatari sees it: sync 34, border 29, disp 200, 47 border, 3 ?? = 313, vbi@310 +// 47 bottom border lines seesm to be too much, some intros have artifacts in the lower lines +// 38 bottom border lines seems to be good + +// 60Hz sync 5, border 29, disp 200, 29 border = 263, vbi@261 + +// 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 + input pal56, // use a 56 hz mode if pal mode is selected + + output [121:0] mode_str +); + +// --------------------------------------------------------------------------- +// ---------------------------- generic timing parameters -------------------- +// --------------------------------------------------------------------------- + +// 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 + +// --------------------------------------------------------------------------- +// ----------------------------- pal56 timing ------------------------------- +// --------------------------------------------------------------------------- + +// 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( + // 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) +); + +// --------------------------------------------------------------------------- +// ----------------------------- pal50 timing ------------------------------- +// --------------------------------------------------------------------------- + +// 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 + +wire [121:0] pal50_config_str; +conf pal50_conf( + // 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) +); + +// --------------------------------------------------------------------------- +// ------------------------------ ntsc timing ------------------------------- +// --------------------------------------------------------------------------- + +// Atari 60Hz low and medium resolution video mode scan doubled: +// total: 1016x526, active incl border: 800x480, displayed: 640x400 +// horizontal scan rate: 15.748 kHz ST, 31.5 kHz VGA, vertical scan rate: 59.88 hz + +wire [121:0] ntsc_config_str; +conf ntsc_conf( + // 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) +); + +// --------------------------------------------------------------------------- +// ------------------------------ mono timing ------------------------------- +// --------------------------------------------------------------------------- + +// Atari 71Hz high resolution video mode: +// total: 896x501, displayed: 640x400 +// horizontal scan rate: 35.714 kHz ST/VGA, vertical scan rate: 71.286 hz + +wire [121:0] mono_config_str; +conf mono_conf( + // 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) +); + + +// this is the video mode multiplexer ... +assign mode_str = mono?mono_config_str:(pal?(pal56?pal56_config_str:pal50_config_str):ntsc_config_str); + +endmodule + +// --------------------------------------------------------------------------- +// ------------------ video timing config string generator ------------------- +// --------------------------------------------------------------------------- + +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 parameters are assembled into one config string +wire [60:0] h_str = { h_sp, + 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_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 diff --git a/tests/verilator/video/video_tb.cpp b/tests/verilator/video/video_tb.cpp new file mode 100644 index 0000000..797403d --- /dev/null +++ b/tests/verilator/video/video_tb.cpp @@ -0,0 +1,606 @@ +#include + +#include "Vvideo.h" +#include "verilated.h" +#include "verilated_vcd_c.h" + +// analyze video mode and compare with: +// http://alive.atari.org/alive9/ovrscn1.php + +// TODO: +// - ST hsync timing +// - hsync 16 pixel later +// - use shifter sync signals for ST directly +// - NTSC timing from Troed/Overscan +// - v-event position from Troed (4 cycles before DE in ST LOW) +// Synthesis: +// - OSD right border +// - check STE sound + +#define DUMP 1 +#define VIKING 0 // enable viking card +#define REZ 0 // LOW=0, MID=1, HI=2 +#define SD 1 // scan doubler on/off +#define SL 2 // scanlines 0=off -> 3=75% +#define PAL 1 // 0-NTSC or 1-PAL +#define PAL56 0 // enable PAL56 mode + +#define STE_SHIFT 0 // 1 +#define STE_LINE_OFFSET 0 + +#define CLK (31875000.0) + +#if VIKING +#define W 900 +#define H 540 +#else +#if REZ==0 && SD==0 +#define W 513 +#else +#define W 1026 +#endif + +#if REZ==2 || SD==1 +#define H 626 +#else +#define H 313 +#endif +#endif + +unsigned char vidmem[1280*1024/8]; + +SDL_Surface* screen = NULL; + +Vvideo* top = NULL; +#if DUMP +VerilatedVcdC* tfp = NULL; +#endif + +double time_ns = 0; + +#define MHZ2NS(a) (1000000000.0/(a)) + +void hexdump(void *data, int size) { + int i, b2c, n=0; + char *ptr = (char*)data; + + if(!size) return; + + while(size>0) { + printf(" %04x: ", n); + b2c = (size>16)?16:size; + for(i=0;ipixels; + // Set the pixel +#if VIKING + // average half size + if((x < 2*W) && (y < 2*H)) { + pixel = (pixel>>2)&0x3f3f3f; + if(!(y&1) && !(x&1)) + pixels[ ( y/2 * screen->w ) + x/2 ] = pixel; + else + pixels[ ( y/2 * screen->w ) + x/2 ] += pixel; + } +#else + if((x < W) && (y < H)) + pixels[ ( y * screen->w ) + x ] = pixel; +#endif +} + +int dump_enabled = 0; + +void eval(void) { + // evaluate recent changes + top->eval(); + +#if DUMP + if(dump_enabled) + tfp->dump(time_ns); +#endif + + // check if hsync changes + { static int hs = 0; + static double hs_ns = 0; + static double hs_hi_ns = 0; + static double hs_lo_ns = 0; + static double hs_tot_ns = 0; + static unsigned long last_addr = 0; + static int last_addr_inc = 0; + + if(top->v__DOT__stvid_hs != hs) { + if(hs_ns) { + double hs_time = time_ns - hs_ns; + int change = 0; + + if(hs) { + if(fabs(hs_hi_ns - hs_time) > 0.001) change = 1; + hs_hi_ns = hs_time; + } else { + if(fabs(hs_lo_ns - hs_time) > 0.001) change = 1; + hs_lo_ns = hs_time; + } + double hs_tot_ns = hs_lo_ns + hs_hi_ns; + if(change && hs_lo_ns && hs_hi_ns) + printf("HSYNC changed line in %d HI/LO %.3fus/%.3fus, tot %.3fus / %.3fkhz\n", + top->v__DOT__shifter__DOT__vcnt, hs_hi_ns/1000, hs_lo_ns/1000, + hs_tot_ns/1000, 1000000/hs_tot_ns); + } + + hs_ns = time_ns; + hs = top->v__DOT__stvid_hs; + } + } + + // check if vsync changes + { static int vs = 0; + static double vs_ns = 0; + static double vs_hi_ns = 0; + static double vs_lo_ns = 0; + static double vs_tot_ns = 0; + if(top->v__DOT__stvid_vs != vs) { + if(vs_ns) { + double vs_time = time_ns - vs_ns; + int change = 0; + + if(vs) { + if(fabs(vs_hi_ns - vs_time) > 1) change = 1; + vs_hi_ns = vs_time; + } else { + if(fabs(vs_lo_ns - vs_time) > 1) change = 1; + vs_lo_ns = vs_time; + } + double vs_tot_ns = vs_lo_ns + vs_hi_ns; + if(change && vs_lo_ns && vs_hi_ns) + printf("VSYNC HI/LO %.3fms/%.3fms, tot %.3fms / %.3fhz\n", + vs_hi_ns/1000000, vs_lo_ns/1000000, vs_tot_ns/1000000, 1000000000/vs_tot_ns); + } + + vs_ns = time_ns; + vs = top->v__DOT__stvid_vs; + } + } + + // eval on negedge of 8 mhz clk + { static int last_cpu_clk = 0; + if(!top->cpu_clk && last_cpu_clk) { + if(top->read) { + unsigned long long v; +#if VIKING + // viking can address up to 256kb + memcpy(&v, vidmem+2*(top->vaddr&0x1fffc), 8); +#else + memcpy(&v, vidmem+2*(top->vaddr&0x7ffc), 8); +#endif + top->data = + ((v & 0xff00ff00ff00ff00) >> 8) | + ((v & 0x00ff00ff00ff00ff) << 8); + + // Bus cycles 0 and 2 may be used by video + // Usually shifter uses 0 (incl STE DMA audio) + // Viking uses 2 + // And MISTXVID uses 0 and 2 (and thus doesn't support STE DMA audio) + if((top->bus_cycle != 0)&&(top->bus_cycle != 2)) { + printf("illegal read in bus_cycle %d\n", top->bus_cycle); + exit(-1); + } + } + } + last_cpu_clk = top->cpu_clk; + } + + // eval on negedge of 32 mhz clk + if(dump_enabled) { + static int last_clk = 0; + // scan doubled output is always analyzed at 32MHz + if(! +#if VIKING + top->clk_128 +#elif SD + top->clk_32 +#else + top->v__DOT__shifter__DOT__pclk +#endif + && last_clk) { + static int last_hs=0, last_vs=0; + static int x=0, y=0; + + put_pixel32(x, y, + (top->video_r<<18) + (top->video_g<<10) + (top->video_b<<2)); + + // draw hsync in dark red + if(top->v__DOT__stvid_hs == top->v__DOT__osd__DOT__hs_pol) { + put_pixel32(x, y, 0x800000); + + // all pixels should be black during sync, highlight other ones in green + if((top->video_r != 0) || (top->video_g != 0) || (top->video_b != 0)) + put_pixel32(x, y, 0x00ff00); + } + + x++; + if(top->v__DOT__stvid_hs != last_hs) { + // and of hsync + if(last_hs == top->v__DOT__osd__DOT__hs_pol) + { x = 0; y++; } + last_hs = top->v__DOT__stvid_hs; + + /* update the screen */ + SDL_UpdateRect(screen, 0, 0, 0, 0); + } + if(top->v__DOT__stvid_vs != last_vs) { + if(top->v__DOT__stvid_vs) y = 0; + last_vs = top->v__DOT__stvid_vs; + } + } +#if VIKING + last_clk = top->clk_128; +#elif SD + last_clk = top->clk_32; +#else + last_clk = top->v__DOT__shifter__DOT__pclk; +#endif + } +} + +unsigned long cpu_write_addr = 0; +unsigned short cpu_write_data; + +// advance time and create valid 8 Mhz clock and signals +// derived from it +void wait_ns(double n) { + static double clk_time = 0; + + eval(); + + // check if next clk event is within waiting period + while(clk_time <= n) { + time_ns += clk_time; // advance time to next clk event + n -= clk_time; // reduce remainung waiting time + + // process change on clk +#if VIKING + // viking needs 128MHz + top->clk_128 = !top->clk_128; + eval(); + static int x = 0; + if(x++ == 3) { + x = 0; +#else + { +#endif + + top->clk_32 = !top->clk_32; + eval(); + + // things supposed to happen on rising clock edge + if(top->clk_32) { + // every 4th cycle ... + static int clk_cnt = 0; + + if(clk_cnt == 1) + top->bus_cycle = (top->bus_cycle + 1) &3; + + clk_cnt = clk_cnt + 1; + top->cpu_clk = (clk_cnt&2)?1:0; // 8MHz + + if(clk_cnt == 4) clk_cnt = 0; + + // ------------ cpu access --------------- + if(clk_cnt == 2) { + top->cpu_sel = 0; + + if(top->bus_cycle == 0) { + + // perform cpu write access + if(cpu_write_addr) { + printf("CPU WRITE $%lx = $%x\n", cpu_write_addr, cpu_write_data); + + top->cpu_sel = (cpu_write_addr & ~0xff) == 0xff8200; + top->cpu_addr = (cpu_write_addr & 0xff)>>1; + top->cpu_rw = 0; + top->cpu_din = cpu_write_data; + top->cpu_uds = top->cpu_lds = 0; + cpu_write_addr = 0; + } + } + } + } + } + eval(); + +#if VIKING + clk_time = MHZ2NS(4*CLK)/2.0; // next clk change in 3.9ns +#else + clk_time = MHZ2NS(CLK)/2.0; // next clk change in 31.25ns +#endif + } + + // next event is when done waiting + time_ns += n; // advance time + clk_time -= n; +} + +void wait_us(double n) { + wait_ns(n * 1000.0); +} + +void wait_ms(double n) { + wait_us(n * 1000.0); +} + +void cpu_write_short(unsigned long addr, unsigned short data) { + cpu_write_addr = addr; + cpu_write_data = ((data & 0xff)<<8) | ((data & 0xff00)>>8); + wait_us(1); // wait two 2MHz system cycles +} + +int main(int argc, char **argv, char **env) { + +#if STE_SHIFT != 0 +#if REZ == 0 +#define XTRA_OFFSET (8+2*STE_LINE_OFFSET) // 16 pixels * 4 bit +#elif REZ == 1 +#define XTRA_OFFSET (4+2*STE_LINE_OFFSET) // 16 pixels * 2 bit +#else +#define XTRA_OFFSET (2+2*STE_LINE_OFFSET) // 16 pixels * 1 bit +#endif +#else +#define XTRA_OFFSET (0+2*STE_LINE_OFFSET) +#endif + + memset(vidmem, 0x80, sizeof(vidmem)); + + // load image +#if VIKING + FILE *in = fopen("viking.raw", "rb"); + if(in) { + fread(vidmem, 1280*1024/8, 1, in); + fclose(in); + } + +#else +#if REZ == 0 + FILE *in = fopen("low.raw", "rb"); +#elif REZ == 1 + FILE *in = fopen("mid.raw", "rb"); +#else + FILE *in = fopen("high.raw", "rb"); +#endif + if(in) { + // load single lines with offset if wanted + int i; + unsigned char *p = vidmem; + for(i=0;i<200;i++) { + fread(p, 160, 1, in); + p += 160+XTRA_OFFSET; + } + fclose(in); + } +#endif + +#if 0 + // add some test pattern to the begin + { + int x; for(x=0;x<8;x++) { + // top left + vidmem[x*(160+XTRA_OFFSET)+0] = 0x55; vidmem[x*(160+XTRA_OFFSET)+1] = 0x55; + vidmem[x*(160+XTRA_OFFSET)+2] = 0x33; vidmem[x*(160+XTRA_OFFSET)+3] = 0x33; + vidmem[x*(160+XTRA_OFFSET)+4] = 0x0f; vidmem[x*(160+XTRA_OFFSET)+5] = 0x0f; + vidmem[x*(160+XTRA_OFFSET)+6] = 0x00; vidmem[x*(160+XTRA_OFFSET)+7] = 0xff; + + // top right + vidmem[x*(160+XTRA_OFFSET)+152+XTRA_OFFSET] = 0x55; + vidmem[x*(160+XTRA_OFFSET)+153+XTRA_OFFSET] = 0x55; + vidmem[x*(160+XTRA_OFFSET)+154+XTRA_OFFSET] = 0x33; + vidmem[x*(160+XTRA_OFFSET)+155+XTRA_OFFSET] = 0x33; + vidmem[x*(160+XTRA_OFFSET)+156+XTRA_OFFSET] = 0x0f; + vidmem[x*(160+XTRA_OFFSET)+157+XTRA_OFFSET] = 0x0f; + vidmem[x*(160+XTRA_OFFSET)+158+XTRA_OFFSET] = 0x00; + vidmem[x*(160+XTRA_OFFSET)+159+XTRA_OFFSET] = 0xff; + }} +#endif + + /* initialize SDL */ + SDL_Init(SDL_INIT_VIDEO); + + /* set the title bar */ + SDL_WM_SetCaption("SDL Test", "SDL Test"); + + /* create window */ + screen = SDL_SetVideoMode(W, H, 0, 0); + + Verilated::commandArgs(argc, argv); + // init top verilog instance + top = new Vvideo; + +#if DUMP + // init trace dump + Verilated::traceEverOn(true); + tfp = new VerilatedVcdC; + top->trace (tfp, 99); + tfp->open ("video.vcd"); +#endif + + // initialize system inputs + top->clk_32 = 1; + +#if REZ == 0 + // setup palette + unsigned char x,coltab[][3] = { + { 7,7,7 }, { 7,0,0 }, { 0,7,0 }, { 7,7,0 }, { 0,0,7 }, { 7,0,7 }, { 0,7,7 }, { 5,5,5 }, + { 3,3,3 }, { 7,3,3 }, { 3,7,3 }, { 7,7,3 }, { 3,3,7 }, { 7,3,7 }, { 3,7,7 }, { 0,0,0 }}; + for(x=0;x<16;x++) { + top->v__DOT__shifter__DOT__palette_r[x] = coltab[x][0]; + top->v__DOT__shifter__DOT__palette_g[x] = coltab[x][1]; + top->v__DOT__shifter__DOT__palette_b[x] = coltab[x][2]; + } +#elif REZ == 1 + // setup palette + unsigned char x,coltab[][3] = { + { 7,7,7 }, { 7,0,0 }, { 0,7,0 }, { 0,0,0 } }; + for(x=0;x<4;x++) { + top->v__DOT__shifter__DOT__palette_r[x] = coltab[x][0]; + top->v__DOT__shifter__DOT__palette_g[x] = coltab[x][1]; + top->v__DOT__shifter__DOT__palette_b[x] = coltab[x][2]; + } +#endif + +#if 1 + // show OSD + top->v__DOT__osd__DOT__enabled = 1; + { int i, j; + for(i=0;i<2048;i++) + top->v__DOT__osd__DOT__buffer[i] = (i&8)?0xf0:0x0f; + + for(i=0;i<256;i++) { + top->v__DOT__osd__DOT__buffer[i] = 0x33; + top->v__DOT__osd__DOT__buffer[i+2048-256] = 0xcc; + } + + for(i=0;i<8;i++) { + for(j=0;j<4;j++) { + top->v__DOT__osd__DOT__buffer[i*256+j+0] = 0x66; + top->v__DOT__osd__DOT__buffer[i*256+j+4] = 0x00; + top->v__DOT__osd__DOT__buffer[i*256+j+8] = 0xff; + top->v__DOT__osd__DOT__buffer[i*256+j+12] = 0x00; + + top->v__DOT__osd__DOT__buffer[i*256+255-j-12] = 0x00; + top->v__DOT__osd__DOT__buffer[i*256+255-j-8] = 0xff; + top->v__DOT__osd__DOT__buffer[i*256+255-j-4] = 0x00; + top->v__DOT__osd__DOT__buffer[i*256+255-j-0] = 0x66; + } + } + } +#endif + + char adjust_x = -1; + char adjust_y = 1; + + top->adjust = 256*(unsigned char)adjust_x + (unsigned char)adjust_y; + + top->scandoubler_disable = SD?0:1; + top->viking_enable = VIKING?1:0; + top->scanlines = SL; + + // reset + wait_ns(100); + top->cpu_reset = 1; + wait_ns(random()%2000); + top->cpu_reset = 0; + + top->v__DOT__shifter__DOT__hcnt = random(); + +#if (STE_SHIFT != 0) || (STE_LINE_OFFSET != 0) + top->ste = 1; + top->v__DOT__shifter__DOT__pixel_offset = STE_SHIFT; + top->v__DOT__shifter__DOT__line_offset = STE_LINE_OFFSET; +#endif + +#if VIKING + wait_ms(13+14.5); +#else + +#if REZ != 2 + // switch to pal 50 hz lowrez + top->pal56 = PAL56; + top->v__DOT__shifter__DOT__shmode = REZ; // lowrez/mid + top->v__DOT__shifter__DOT__syncmode = PAL?2:0; // pal + +#if PAL +#if PAL56 && SD +#define IMGTIME (612 * 928 * 1000 / CLK) + wait_ms(34+2*IMGTIME); // PAL56 +#else +#define IMGTIME (626 * 1024 * 1000 / CLK) // one full image has 626 lines @ 32us = 40.064 + wait_ms(36+2*IMGTIME); // PAL50 +#endif +#else +#define IMGTIME (526 * 1016 * 1000 / CLK) + wait_ms(31+2*IMGTIME); // NTSC +#endif +#else + // skip forward to first image + wait_ms(12); +#endif +#endif + + // fetch image parameters + // top->v__DOT__shifter__DOT__de_h_end + // top->v__DOT__shifter__DOT__t0_h_border_right + // t1_h_blank_right + // t2_h_sync + // t3_h_blank_left + // t4_h_border_left + // t6_v_border_bot + // t7_v_blank_bot + // t8_v_sync + // t9_v_blank_top + // t10_v_border_top + // t11_v_end + +#if !VIKING +#if REZ==0 +#define PCLK CLK/4 +#elif REZ==1 +#define PCLK CLK/2 +#else +#define PCLK CLK +#endif + printf("Timing:\n"); + printf("Total: %d\n", top->v__DOT__shifter__DOT__t5_h_end+1); + printf("HFreq: %.3fkHz\n", PCLK/1000/(top->v__DOT__shifter__DOT__t5_h_end+1)); + + // v__DOT__shifter__DOT__config_string[2U] +#endif + + printf("DUMP ENABLE\n"); + dump_enabled = 1; + + // verify scan doubler state + // printf("Scandoubler:\n"); + // int total = top->v__DOT__scandoubler__DOT__hs_low + top->v__DOT__scandoubler__DOT__hs_high + 2; + // printf(" Hor total = %d -> %.3fkhz\n", total, CLK/2000.0/total); + + // printf("scan doubler is %s\n", top->v__DOT__scandoubler_enabled?"enabled":"disabled"); + +#if VIKING + wait_ms(14); +#else +#if REZ != 2 +#if PAL +#if PAL56 && SD // PAL56 + wait_ms(18); +#else + wait_ms(21); // PAL50 +#endif +#else + wait_ms(18); // NTSC +#endif +#else + wait_ms(16); +#endif +#endif + +#if DUMP + tfp->close(); +#endif + + getchar(); + + /* cleanup SDL */ + SDL_Quit(); + + exit(0); + } + diff --git a/tests/verilator/video/viking.raw b/tests/verilator/video/viking.raw new file mode 100644 index 0000000..baf2249 Binary files /dev/null and b/tests/verilator/video/viking.raw differ diff --git a/tests/verilator/video/viking.v b/tests/verilator/video/viking.v new file mode 100644 index 0000000..fb25232 --- /dev/null +++ b/tests/verilator/video/viking.v @@ -0,0 +1,155 @@ +// +// viking.v +// +// Atari ST(E) Viking/SM194 +// 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 . + +// The viking card does not have its own CPU interface as it is not +// configurable in any way. It just etches data from ram and displays +// it on screen. + +module viking ( + input pclk, // 128 MHz pixel clock + + // memory interface + input himem, // use memory behind rom + input bclk, // 8 MHz bus clock + input [1:0] bus_cycle, // bus-cycle for bus access sync + output reg [22:0] addr, // video word address + output read, // video read cycle + input [63:0] data, // video data read + + // VGA output (multiplexed with sm124 output in top level) + output hs, + output vs, + output [3:0] r, + output [3:0] g, + output [3:0] b +); + +localparam BASE = 23'h600000; // c00000 +localparam BASE_HI = 23'h740000; // e80000 + +// total width must be multiple of 64, so video runs synchronous +// to main bus + +// Horizontal timing +// HBP1 | H | HFP | HS | HBP2 +// -----|XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|-----|____|----- +// HBP1 is used for prefetch + +// 1280x +localparam H = 1280; +localparam HFP = 88; +localparam HS = 136; +localparam HBP1 = 32; +localparam HBP2 = 192; + +// Vertical timing +// V | VFP | VS | VBP +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|-----|____|----- + +// x1024 +localparam V = 1024; +localparam VFP = 9; +localparam VS = 4; +localparam VBP = 9; + +assign read = (bus_cycle == 2) && me; // memory enable can directly be used as a ram read signal + +// --------------------------------------------------------------------------- +// --------------------------- internal state counter ------------------------ +// --------------------------------------------------------------------------- + +reg [3:0] t; +always @(posedge pclk) begin + // 128 Mhz counter synchronous to 8 Mhz clock + // force counter to pass state 0 exactly after the rising edge of clk_reg (8Mhz) + if(((t == 4'd15) && ( bclk == 0)) || + ((t == 4'd0) && ( bclk == 1)) || + ((t != 4'd15) && (t != 4'd0))) + t <= t + 4'd1; +end + +// create internal bus_cycle signal which is stable on the positive clock +// edge and extends the previous state by half a 128 Mhz clock cycle +reg [5:0] bus_cycle_L; +always @(negedge pclk) + bus_cycle_L <= { bus_cycle, t }; + + +// --------------- horizontal timing ------------- +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 "video" bus cyle (0) + // cpu has cycles 1 and 3 + if(bus_cycle_L == { 2'd1, 4'd15 }) + h_cnt<=0; + end else + h_cnt <= h_cnt + 1; +end + +// --------------- vertical timing ------------- +reg[10:0] v_cnt; // 0..2047 +assign vs = ((v_cnt >= V+VFP) && (v_cnt < V+VFP+VS))?0:1; +always@(posedge pclk) begin + if(h_cnt==HBP1+H+HFP+HS+HBP2-1) begin + if(v_cnt==V+VFP+VS+VBP-1) v_cnt <= 0; + else v_cnt <= v_cnt+1; + end +end + +reg [63:0] input_latch; +reg [63:0] shift_register; + +// ---------------- memory timing ---------------- +always@(posedge pclk) begin + // last line on screen + if(v_cnt == V+VFP+VS+VBP-2) + addr <= himem?BASE_HI:BASE; + else if(me && bus_cycle_L == 6'h30) // directly after read + addr <= addr + 23'd4; // advance 4 words (64 bits) + + if(me && (bus_cycle_L == 6'h2f)) + input_latch <= data; + + if(bus_cycle_L == 6'h3f) + // 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 + +// memory enable (data is being read from memory) +wire me = (v_cnt < V)&&(h_cnt < H); +// display enable (data is being displayed) +wire de = (v_cnt < V)&&(h_cnt >= HBP1)&&(h_cnt < HBP1+H); + +wire pix = de?(!shift_register[63]):1'b0; + +// 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 diff --git a/tests/verilator/video/xvid.raw b/tests/verilator/video/xvid.raw new file mode 100644 index 0000000..4c4311d Binary files /dev/null and b/tests/verilator/video/xvid.raw differ