1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-07 16:31:17 +00:00

[ATARI ST] Scandoubler optional, video test bench

This commit is contained in:
harbaum
2015-02-18 20:08:59 +00:00
parent 2583075eaf
commit bdc4d206e8
18 changed files with 2503 additions and 4 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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 &

Binary file not shown.

Binary file not shown.

Binary file not shown.

175
tests/verilator/video/osd.v Normal file
View File

@@ -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 <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
module 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

View File

@@ -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

View File

@@ -0,0 +1,167 @@
//
// scandoubler.v
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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

View File

@@ -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 <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
module shifter (
// system interface
input clk, // 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

View File

@@ -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 <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
module 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

View File

@@ -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

View File

@@ -0,0 +1,264 @@
//
// video.v
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
module 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

View File

@@ -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 <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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

View File

@@ -0,0 +1,606 @@
#include <SDL.h>
#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;i<b2c;i++) printf("%02x ", 0xff&ptr[i]);
printf(" ");
for(i=0;i<(16-b2c);i++) printf(" ");
for(i=0;i<b2c;i++) printf("%c", isprint(ptr[i])?ptr[i]:'.');
printf("\n");
ptr += b2c;
size -= b2c;
n += b2c;
}
}
void put_pixel32(int x, int y, Uint32 pixel ) {
// Convert the pixels to 32 bit
Uint32 *pixels = (Uint32 *)screen->pixels;
// 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);
}

Binary file not shown.

View File

@@ -0,0 +1,155 @@
//
// viking.v
//
// Atari ST(E) Viking/SM194
// http://code.google.com/p/mist-board/
//
// Copyright (c) 2013-2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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

Binary file not shown.