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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
26
tests/verilator/video/Makefile
Normal file
26
tests/verilator/video/Makefile
Normal 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 &
|
||||
BIN
tests/verilator/video/high.raw
Normal file
BIN
tests/verilator/video/high.raw
Normal file
Binary file not shown.
BIN
tests/verilator/video/low.raw
Normal file
BIN
tests/verilator/video/low.raw
Normal file
Binary file not shown.
BIN
tests/verilator/video/mid.raw
Normal file
BIN
tests/verilator/video/mid.raw
Normal file
Binary file not shown.
175
tests/verilator/video/osd.v
Normal file
175
tests/verilator/video/osd.v
Normal 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
|
||||
31
tests/verilator/video/readme.txt
Normal file
31
tests/verilator/video/readme.txt
Normal 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
|
||||
167
tests/verilator/video/scandoubler.v
Normal file
167
tests/verilator/video/scandoubler.v
Normal 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
|
||||
704
tests/verilator/video/shifter.v
Normal file
704
tests/verilator/video/shifter.v
Normal 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
|
||||
136
tests/verilator/video/sync_adjust.v
Normal file
136
tests/verilator/video/sync_adjust.v
Normal 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
|
||||
52
tests/verilator/video/video.sav
Normal file
52
tests/verilator/video/video.sav
Normal 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
|
||||
264
tests/verilator/video/video.v
Normal file
264
tests/verilator/video/video.v
Normal 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
|
||||
183
tests/verilator/video/video_modes.v
Normal file
183
tests/verilator/video/video_modes.v
Normal 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
|
||||
606
tests/verilator/video/video_tb.cpp
Normal file
606
tests/verilator/video/video_tb.cpp
Normal 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);
|
||||
}
|
||||
|
||||
BIN
tests/verilator/video/viking.raw
Normal file
BIN
tests/verilator/video/viking.raw
Normal file
Binary file not shown.
155
tests/verilator/video/viking.v
Normal file
155
tests/verilator/video/viking.v
Normal 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
|
||||
BIN
tests/verilator/video/xvid.raw
Normal file
BIN
tests/verilator/video/xvid.raw
Normal file
Binary file not shown.
Reference in New Issue
Block a user