Files
ZX-Spectrum_MISTer/rtl/ula.sv
2023-05-11 22:49:11 +08:00

320 lines
9.0 KiB
Systemverilog

//
//
// Spectrum Video Controller implementation
// - ZX48, ZX128, Pentagon 128 timings
// - ULA+ v1.1 programmable palette with extended Timex control.
// - Timex video modes
//
// Copyright (c) 2016 Sorgelig
//
//
// 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/>.
//
`timescale 1ns / 1ps
module ULA
(
input reset,
input clk_sys, // master clock
input ce_7mp,
input ce_7mn,
output ce_cpu_sp,
output ce_cpu_sn,
// CPU interfacing
input [15:0] addr,
input [7:0] din,
input nMREQ,
input nIORQ,
input nRFSH,
input nRD,
input nWR,
output nINT,
// VRAM interfacing
output [14:0] vram_addr,
input [7:0] vram_dout,
output [7:0] port_ff,
// ULA+
input ulap_avail,
output ulap_sel,
output [7:0] ulap_dout,
output reg ulap_ena,
output reg ulap_mono,
output [7:0] ulap_color,
// Timex mode
input tmx_avail,
output reg mode512,
// Misc. signals
input snow_ena,
input mZX,
input m128,
input page_scr,
input [2:0] page_ram,
input [2:0] border_color,
input [1:0] wide,
// Video outputs
output reg HSync,
output reg VSync,
output reg HBlank,
output reg VBlank,
output I,R,G,B
);
assign vram_addr = vaddr;
assign nINT = ~INT;
assign port_ff = tmx_using_ff ? {2'b00, tmx_cfg} : mZX ? ff_data : 8'hFF;
// Pixel clock
reg [8:0] hc = 0;
reg [8:0] vc_next;
reg [8:0] vc = 0;
reg [8:0] hc_next;
wire Border_next = ((vc_next[7] & vc_next[6]) | vc_next[8] | hc_next[8]);
reg Border;
reg [4:0] FlashCnt_next;
reg [4:0] FlashCnt;
always @(*) begin
vc_next = vc;
FlashCnt_next = FlashCnt;
if (hc==((mZX && m128) ? 455 : 447)) begin
hc_next = 0;
if (vc == (!mZX ? 319 : m128 ? 310 : 311)) begin
vc_next = 0;
FlashCnt_next = FlashCnt + 1'd1;
end else begin
vc_next = vc + 1'd1;
end
end else begin
hc_next = hc + 1'd1;
end
end
always @(posedge clk_sys) begin
reg m512;
if(ce_7mp) hiSRegister <= {hiSRegister[14:0],1'b0};
if(ce_7mn) begin
vc <= vc_next;
hc <= hc_next;
Border <= Border_next;
FlashCnt <= FlashCnt_next;
if((vc_next<192) || (hc_next<256)) m512 <= (m512 | tmx_hi);
if (hc_next == 0) begin
if(mZX ? (vc_next == 240) : (vc_next == 248)) begin
mode512 <= m512;
m512 <= 0;
end
end
if(!mZX) begin
if (hc_next == 312) HBlank <= 1;
else if (hc_next == 420) HBlank <= 0;
if (hc_next == 338) HSync <= 1;
else if (hc_next == 370) HSync <= 0;
end else if(m128) begin
if (hc_next == 312) HBlank <= 1;
else if (hc_next == 424) HBlank <= 0;
if (hc_next == 340) HSync <= 1; //ULA 6C
else if (hc_next == 372) HSync <= 0; //ULA 6C
end else begin
if (hc_next == 312) HBlank <= 1;
else if (hc_next == 416) HBlank <= 0;
if (hc_next == 336) HSync <= 1; //ULA 5C
else if (hc_next == 368) HSync <= 0; //ULA 5C
end
if(mZX) begin
if(vc_next == 240) VSync <= 1;
else if (vc_next == 244) VSync <= 0;
if(vc_next == 236) VBlank <= 1;
else if(vc_next == 264) VBlank <= 0;
end else begin
if(vc_next == 248) VSync <= 1;
else if (vc_next == 256) VSync <= 0;
if(vc_next == 236) VBlank <= 1;
else if(vc_next == 272) VBlank <= 0;
end
case(wide)
1: HBlank <= !(hc_next < 290 || hc_next >= ((mZX && m128) ? 455-11 : 447-11));
2: begin
HBlank <= hc_next >= 278 || hc_next < 2;
VBlank <= (vc_next >= 204) && (mZX ? (m128 ? (vc <= 298) : (vc <= 299)) : (vc_next <= 307));
end
endcase
if( mZX && (vc_next == 248) && (hc_next == (m128 ? 8 : 4))) INT <= 1;
if(!mZX && (vc_next == 239) && (hc_next == 326)) INT <= 1;
if(INT) INTCnt <= ((m128 && INTCnt == 71) || (~m128 && INTCnt == 63)) ? 7'd0 : (INTCnt + 1'd1);
if(INTCnt == 0) INT <= 0;
if(hc_next[2:0] == 4) begin
SRegister <= VidEN ? bits : 8'd0;
hiSRegister <= VidEN ? {bits, attr} : 16'd0;
AttrOut <= tmx_hi ? hiattr : VidEN ? attr : {2'b00,border_color,border_color};
end else begin
SRegister <= {SRegister[6:0], 1'b0};
hiSRegister <= {hiSRegister[14:0],1'b0};
end
//1T update for border in Pentagon mode
if(!mZX & ((hc_next<12) | (hc_next>267) | (vc>=192))) AttrOut <= tmx_hi ? hiattr : {2'b00,border_color,border_color};
if(hc_next[3]) VidEN <= ~Border;
if(~Border_next) begin
if(~tmx_cfg[1]) case(hc_next[3:0])
'h8,'hC: vaddr <= {stdpage ? page_scr : tmx_cfg[2],tmx_cfg[0],vc[7:6],vc[2:0],vc[5:3],hc_next[7:4],hc_next[2]};
'hA,'hE: vaddr[14:7] <= {stdpage ? page_scr : tmx_cfg[2],tmx_cfg[0],3'b110,vc[7:5]}; // CAS and page(?) only
endcase
if(tmx_cfg[1]) case(hc_next[3:0])
'h8,'hC: vaddr <= {stdpage ? page_scr : tmx_cfg[0],1'b0,vc[7:6],vc[2:0],vc[5:3],hc_next[7:4], hc_next[2]};
'hA,'hE: vaddr[14:7] <= {stdpage ? page_scr : tmx_cfg[0],1'b1,vc[7:6],vc[2:0],vc[5]}; // CAS and page(?) only
endcase
// Snow effect for ULA-48 only. ULA-128 has no snow bug.
if (mZX & ~m128 & ~nMREQ & ~nRFSH & contendAddr & snow_ena) case(hc_next[3:0])
'h8,'hC: vaddr[6:0] <= addr[6:0]; // only RAS got replaced
endcase
case(hc_next[3:0])
'h9,'hD: bits <= vram_dout;
'hB,'hF: attr <= vram_dout;
endcase
if(hc_next[3] & hc_next[0]) ff_data <= vram_dout;
end
if (hc_next[3:0] == 1) ff_data <= 255;
end
end
wire [7:0] hipalette[8];
assign hipalette = '{8'b01111000, 8'b01110001, 8'b01101010, 8'b01100011,
8'b01011100, 8'b01010101, 8'b01001110, 8'b01000111};
reg INT = 0;
reg [6:0] INTCnt = 1;
reg [7:0] ff_data;
reg [7:0] SRegister;
reg [15:0] hiSRegister;
reg [14:0] vaddr;
reg [7:0] AttrOut;
reg VidEN = 0;
reg [7:0] bits;
reg [15:0] hibits;
reg [7:0] attr;
wire [7:0] hiattr = hipalette[tmx_cfg[5:3]];
wire stdpage = tmx_using_ff | ~tmx_ena;
wire Pixel = tmx_hi ? hiSRegister[15] : SRegister[7] ^ (AttrOut[7] & FlashCnt[4]);
assign {I,G,R,B} = Pixel ? {AttrOut[6],AttrOut[2:0]} : {AttrOut[6],AttrOut[5:3]};
assign ulap_color = palette[(tmx_hi ? hiSRegister[15] : SRegister[7]) ? {AttrOut[7:6],1'b0,AttrOut[2:0]} : {AttrOut[7:6],1'b1,AttrOut[5:3]}];
///////////////////////////////////////////////////////////////////////////////
reg CPUClk;
reg ioreqtw3;
reg mreqt23;
wire ioreq_n = (addr[0] & ~(ulap_acc & ulap_avail)) | nIORQ;
wire clkwait_next = hc_next[2] | hc_next[3];
wire ulaContend = clkwait_next & ~Border_next & CPUClk & ioreqtw3;
wire contendAddr = ((addr[15:14] == 2'b01) | (m128 & (addr[15:14] == 2'b11) & page_ram[0]));
wire memContend = ioreq_n & mreqt23 & contendAddr;
wire ioContend = ~ioreq_n;
wire next_clk = hc_next[0] | (mZX & ulaContend & (memContend | ioContend));
reg next_clk_r;
assign ce_cpu_sp = ce_7mn & (~CPUClk & next_clk_r);
assign ce_cpu_sn = ce_7mn & ( CPUClk & ~next_clk_r);
always @(posedge clk_sys) begin
if(ce_7mn) CPUClk <= next_clk;
if(ce_7mp) next_clk_r <= next_clk;
if(~CPUClk) begin
// These are transparent latches!
ioreqtw3 <= ioreq_n;
mreqt23 <= nMREQ;
end
end
///////////////// ULA+, Timex ///////////////////
assign ulap_dout = ulap_group ? {6'd0, ulap_mono, ulap_ena} : palette_q;
assign ulap_sel = ulap_acc & addr[14] & ulap_avail;
wire ulap_acc = ({addr[15], 1'b0, addr[13:0]} == 'hBF3B);
wire io_wr = ~nIORQ & ~nWR;
reg [5:0] pal_addr;
reg ulap_group;
reg [7:0] palette[64];
reg [7:0] palette_q;
reg [5:0] tmx_cfg;
reg tmx_ena;
reg tmx_using_ff;
wire tmx_hi = &{tmx_ena, tmx_cfg[2:1]};
always @(posedge clk_sys) begin
reg old_wr;
old_wr <= io_wr;
palette_q <= palette[pal_addr];
if(reset) begin
{ulap_ena, tmx_ena, tmx_using_ff, tmx_cfg} <= 0;
palette <= '{default:0};
end else if(~old_wr & io_wr) begin
if(ulap_acc & ulap_avail) begin
if(addr[14]) begin
if(ulap_group) {ulap_mono,ulap_ena} <= din[1:0];
else palette[pal_addr] <= din;
end else begin
case(din[7:6])
0: {ulap_group, pal_addr} <= {1'b0, din[5:0]};
1: begin
ulap_group <= 1;
if(!tmx_using_ff) {tmx_ena, tmx_cfg} <= {|din[2:0], din[5:0]};
end
default: ;
endcase
end
end
if((addr[7:0] == 'hFF) & tmx_avail) {tmx_using_ff, tmx_ena, tmx_cfg} <= {1'b1, |din[2:0], din[5:0]};
end
end
endmodule