SE-VGA/se-xga.sv

342 lines
13 KiB
Systemverilog

/******************************************************************************
* SE-VGA
* Top-level module
* techav
* 2021-10-12
******************************************************************************
* This is ... mostly working. It has some write glitches and a vertical line
* five pixels from the left side of the screen.
*****************************************************************************/
module sevga (
input wire nReset, // System reset signal
input wire pixClk, // 65MHz pixel clock
output wire nhSync, // HSync signal
output wire nvSync, // VSync signal
output wire vidOut, // 1-bit Monochrome video signal
output logic [14:0] vramAddr, // VRAM Address bus
inout logic [7:0] vramData, // VRAM Data bus
output wire nvramOE, // VRAM Read strobe
output wire nvramWE, // VRAM Write strobe
output wire nvramCE0, // VRAM Main chip select signal
output wire nvramCE1, // VRAM Alt chip select signal
input logic [23:1] cpuAddr, // CPU Address bus
input logic [15:0] cpuData, // CPU Data bus
input wire ncpuAS, // CPU Address Strobe signal
input wire ncpuUDS, // CPU Upper Data Strobe signal
input wire ncpuLDS, // CPU Lower Data Strobe signal
input wire cpuRnW, // CPU Read/Write select signal
input logic [2:0] ramSize // Select installed RAM size
);
/******************************************************************************
* Initial Video Signal Timing
* The following four functions establish the basic XGA signal timing and
* assert the horizontal and vertical sync signals as appropriate.
* These functions are the minimum required for a signal presence detect test.
*****************************************************************************/
logic [10:0] hCount; // 0..1343
logic [9:0] vCount; // 0..805
wire nhSyncInner;
// Primary video sync counters -- Now more synchronous!
always @(negedge pixClk) begin
if(hCount < 11'd1343) hCount <= hCount + 11'd1;
else begin
hCount <= 11'd0;
if(vCount < 10'd805) vCount <= vCount + 10'd1;
else vCount <= 10'd0;
end
end
// horizontal and vertical sync signals
always_comb begin
//if(hCount >= 11'd1048 && hCount < 11'd1184) nhSyncInner <= 0;
if(hCount >= 11'd1052 && hCount < 11'd1187) nhSyncInner <= 0;
else nhSyncInner <= 1;
nhSync <= nhSyncInner;
if(vCount >= 10'd729 && vCount < 10'd735) nvSync <= 0;
else nvSync <= 1;
end
/******************************************************************************
* Useful signals
* Here we break out a few useful signals, derived from the timing above, that
* will help us elsewhere.
*****************************************************************************/
wire hActive, vActive; // active video signals. vidout black when negated
wire vidActive; // active when both hActive and vActive asserted
wire hLoad; // load pixel data from vram when asserted
assign vidActive = hActive & vActive;
always_comb begin
if(hCount >= 3 && hCount < 1027) hActive <= 1;
else hActive <= 0;
if(vCount >= 0 && vCount < 684) vActive <= 1;
else vActive <= 0;
if(hCount >= 0 && hCount < 1024 && vActive) hLoad <= 1;
else hLoad <= 0;
end
/******************************************************************************
* Primary State Machine
* This is the primary state machine which runs the entire system, handling
* VRAM reads, VRAM writes, VIA writes, and idle states
*****************************************************************************/
// used to align primary state machine with horizontal counter
wire [3:0] vSeq = hCount[3:0];
// define state machine states (Gray code)
parameter
S0 = 4'b0000, // VRAM Read 0
S1 = 4'b0001, // VRAM Read 1
S2 = 4'b0011, // Idle
S3 = 4'b0010, // VRAM Write Upper 0
S4 = 4'b0110, // VRAM Write Upper 1
S5 = 4'b0111, // VRAM Write Lower 0
S6 = 4'b0101, // VRAM Write Lower 1
S7 = 4'b0100, // VIA Write
S8 = 4'b1100, // VSync (to be added later)
S9 = 4'b1101, // undefined
S10 = 4'b1111, // undefined
S11 = 4'b1110, // undefined
S12 = 4'b1010, // undefined
S13 = 4'b1011, // undefined
S14 = 4'b1001, // undefined
S15 = 4'b1000; // undefined
logic [3:0] pState;
// And here is the much simplified primary state machine
always @(negedge pixClk or negedge nReset) begin
if(!nReset) pState <= S2; // resync on reset by jumping to idle state
else begin
case(pState)
S0: pState <= S1; // first VRAM read state, always move to S1
S3: pState <= S4; // first UDS write state, always move to S4
S5: pState <= S6; // first LDS write state, always move to S6
/*S7: begin
pState <= S2;
end*/
S2: begin
// here is where everything actually happens.
if(vSeq == 4'hF) pState <= S0; // time for a read state
else if(cpuUWriteReq && !cpuUWriteSrv && vSeq < 4'hD) pState <= S3;
else if(cpuLWriteReq && !cpuLWriteSrv && vSeq < 4'hD) pState <= S5;
else if(cpuVIAReq && !cpuVIASrv && vSeq < 4'hE) pState <= S7;
else pState <= S2;
end
default: pState <= S2; // everyone ends up at S2 (idle)
endcase
end
end
// primary VRAM signal combination, based on the primary state machine
always_comb begin
// VRAM Read Strobe
if((pState == S0 || pState == S1) && hLoad) nvramOE <= 0;
else nvramOE <= 1;
// VRAM Write Strobe
if(pState == S3 || pState == S5) nvramWE <= 0;
else nvramWE <= 1;
// VRAM Chip Enable Signals
case(pState)
S0, S1: begin
if(hLoad) begin
nvramCE0 <= ~vidBufSel;
nvramCE1 <= vidBufSel;
end else begin
nvramCE0 <= 1;
nvramCE1 <= 1;
end
end
S3, S4, S5, S6: begin
nvramCE0 <= ~cpuBufSel;
nvramCE1 <= cpuBufSel;
end
default: begin
nvramCE0 <= 1;
nvramCE1 <= 1;
end
endcase
// VRAM Address Bus
case(pState)
S0, S1: begin
// address bus for read cycles
if(hLoad) begin
vramAddr[14:6] <= vCount[9:1];
vramAddr[5:0] <= hCount[9:4];
end else begin
vramAddr <= 0;
end
end
S3, S4: begin
// address bus for upper write cycles
vramAddr[14:1] <= cpuAddrShift;
vramAddr[0] <= 0;
end
S5, S6: begin
// address bus for lower write cycles
vramAddr[14:1] <= cpuAddrShift;
vramAddr[0] <= 1;
end
default: begin
// address bus for idle cycles
vramAddr <= 0;
end
endcase
// VRAM Data bus
case(pState)
S3, S4 : vramData <= cpuData[15:8];
S5, S6 : vramData <= cpuData[7:0];
default: vramData <= 8'hZ;
endcase
end
/******************************************************************************
* Video Output Sequencing
* Here is the primary video output shift register sequencing.
* With these functions in place, it should be possible to strap the VRAM data
* signals and see the strapped pattern output on screen.
*****************************************************************************/
logic [8:0] vidData; // the video data we are displaying
// output shift register
always @(posedge pixClk) begin
if(pState == S1 && hLoad) begin
// store VRAM data in shift register
vidData[7:0] <= vramData;
end else if(!hCount[0] && vidActive) begin
// shift out video data
vidData[8:1] <= vidData[7:0];
vidData[0] <= 1;
end
end
// final video output
always_comb begin
if(vidActive) vidOut <= ~vidData[8];
else vidOut <= 0;
end
/******************************************************************************
* CPU Bus Snooping
* Watch the CPU bus for writes to the video buffer regions of memory and write
* that data to VRAM. VRAM write cycles can occur during vidSeq 1 through 7.
* High-order bytes are passed to VRAM on tick states and low-order bytes are
* passed to VRAM on tock states. After the VRAM writes are complete, state
* machine waits for the CPU cycle to end before returning to idle.
*****************************************************************************/
/* Main framebuffer starts $5900 below the top of RAM, alt frame buffer is
* $8000 below the main frame buffer
* ramSize is used to mask the CPU Address bits [21:19] to select the amount
* of memory installed in the computer. Not all possible ramSize selections
* are valid memory sizes when using 30-pin SIMMs in the Mac SE.
* They may be possible using PDS RAM expansion cards.
* ramSize mainBuffer altBuffer ramTop+1 ramSize Valid? Installed SIMMs
* $7 $3fa700 $3f2700 $400000 4.0MB Y [ 1MB 1MB ][ 1MB 1MB ]
* $6 $37a700 $372700 $380000 3.5MB N
* $5 $2fa700 $2f2700 $300000 3.0MB N
* $4 $27a700 $272700 $280000 2.5MB Y [ 1MB 1MB ][256kB 256kB]
* $3 $1fa700 $1f2700 $200000 2.0MB Y [ 1MB 1MB ][ --- --- ]
* $2 $17a700 $172700 $180000 1.5MB N
* $1 $0fa700 $0f2700 $100000 1.0MB Y [256kB 256kB][256kB 256kB]
* $0 $07a700 $072700 $080000 0.5MB Y [256kB 256kB][ --- --- ]
*/
// keep track of pending CPU write requests and whether they have been serviced
wire cpuUWriteReq, cpuLWriteReq, cpuVIAReq;
reg cpuUWriteSrv, cpuLWriteSrv, cpuVIASrv;
wire cpuBufSel;
wire cpuBufAddr;
reg vidBufSel;
wire [13:0] cpuAddrShift = cpuAddr[14:1] - 14'h1380;
wire cpuBufRange;
// these are some helpful signals that shortcut the CPU buffer & VIA addresses
always_comb begin
/*if(cpuAddr[14:1] >= 14'h1380
&& cpuAddr[14:1] < 14'h3E40) cpuBufRange <= 1;
else cpuBufRange <= 0;*/
cpuBufRange <= (cpuAddr[14:1] >= 14'h1380) & (cpuAddr[14:1] < 14'h3E40);
if(!ncpuAS && !cpuRnW
&& !cpuAddr[23] && !cpuAddr[22] // first two bits always 0
&& !(cpuAddr[21] ^ ramSize[2]) // compare with RAM Size bits
&& !(cpuAddr[20] ^ ramSize[1])
&& !(cpuAddr[19] ^ ramSize[0])
&& cpuAddr[18] && cpuAddr[17] // next three bits always 1
&& cpuAddr[16] // skip 15, it selects buffers
&& cpuBufRange // only select buffer addresses
) begin
cpuBufAddr <= 1;
end else begin
cpuBufAddr <= 0;
end
cpuBufSel <= ~cpuAddr[15]; // address bit 15 selects buffer
if(cpuBufAddr && !ncpuUDS) cpuUWriteReq <= 1;
else cpuUWriteReq <= 0;
if(cpuBufAddr && !ncpuLDS) cpuLWriteReq <= 1;
else cpuLWriteReq <= 0;
// VIA is in address block $E8,0000 - $EF,FFFF
// VIA register select pins (RS[3:0]) are wired to cpuAddr[12:9]
// VIA Output Register A is selected when RS[3:0]==$F
/*if(!ncpuAS && !cpuRnW && !ncpuUDS
&& cpuAddr[23] && cpuAddr[22] // VIA Address Select
&& cpuAddr[21] && !cpuAddr[20]
&& cpuAddr[19]
&& cpuAddr[12] && cpuAddr[11] // VIA ORA
&& cpuAddr[10] && cpuAddr[9]
) cpuVIAReq <= 1;
else cpuVIAReq <= 0;*/
// Mac ROM addresses Data Register A as vBase+vBufA:
// $EF,E1FE + (512*15) = $EF,FFFE
// shift right by one because no A0 and we get $77,FFFF
// This bit is giving me hell, so let's expand it
if(ncpuAS==0 && cpuRnW==0 && ncpuUDS==0
&& cpuAddr == 22'h77FFFF) cpuVIAReq <= 1;
else cpuVIAReq <= 0;
end
// if there's an active CPU request and we've reached the state for servicing
// that CPU request, then set a flag to mark that we have serviced it
always @(posedge pixClk or posedge ncpuAS) begin
if(ncpuAS) begin
cpuUWriteSrv <= 0;
cpuLWriteSrv <= 0;
cpuVIASrv <= 0;
end else begin
if(ncpuAS) begin
cpuUWriteSrv <= 0;
cpuLWriteSrv <= 0;
cpuVIASrv <= 0;
end else begin
if(cpuUWriteReq && pState == S3) cpuUWriteSrv <= 1;
if(cpuLWriteReq && pState == S5) cpuLWriteSrv <= 1;
if(cpuVIAReq && pState == S7) cpuVIASrv <= 1;
end
end
end
// store the video buffer selection bit
always @(posedge pixClk or negedge nReset) begin
if(!nReset) vidBufSel <= 0;
// fine. no video buffer select. we use Main only.
//else if(pState == S7) vidBufSel <= ~cpuData[14];
end
endmodule