diff --git a/README.md b/README.md index 4e00ffd..32d5d4e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # SE-VGA -Mirror the Mac SE video over VGA +Simple CPLD project to mirror the Mac SE video over VGA. No scaling is performed -- the Mac 512x342 video is displayed letterboxed (black borders) in a 640x480 frame. + +Circuit uses a single AFT1508AS-100AU CPLD, 32kx8 15ns SRAM, and a 25.175MHz can oscillator, along with some passives. + +Plugs into SE PDS slot and snoops writes to the frame buffer memory locations. Writes are cached and copied to VRAM. \ No newline at end of file diff --git a/cpusnoop.sv b/cpusnoop.sv new file mode 100644 index 0000000..e1181c7 --- /dev/null +++ b/cpusnoop.sv @@ -0,0 +1,108 @@ +/****************************************************************************** + * SE-VGA + * CPU Bus Snoop + * techav + * 2021-04-06 + ****************************************************************************** + * Watches for writes to frame buffer memory addresses and copies that data + * into VRAM + *****************************************************************************/ + +module cpusnoop ( + input wire nReset, // System Reset signal + input wire pixClock, // 25.175MHz Pixel Clock + input logic [2:0] sequence, // Sequence count (low 3 bits of hCount) + 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 wire cpuClk, // CPU Clock + output logic [12:0] vramAddr, // VRAM Address Bus + inout logic [7:0] vramData, // VRAM Data Bus + output wire nvramWE, // VRAM Write strobe +); + + // framebuffer address (with 4MB RAM installed): 0x3FA700 - 0x3FFFFF + + wire pendWriteLo; // low byte write to VRAM pending + wire pendWriteHi; // high byte write to VRAM pending + logic [12:1] addrCache; // store address for cpu writes to framebuffer + logic [7:0] dataCacheLo; // store data for cpu writes to low byte + logic [7:0] dataCacheHi; // store data for cpu writes to high byte + + // when cpu addresses the framebuffer, save the address + always @(negedge ncpuAS or negedge nReset) begin + if(nReset == 1'b0) begin + addrCache <= 16'h0; + end else begin + if(cpuAddr >= 24'h3FA700 && cpuAddr < 24'h400000) begin + addrCache[12:0] <= cpuAddr - 16'hA700; + end + end + end + + // when cpu addresses the framebuffer, save high byte + always @(negedge ncpuUDS or negedge nReset) begin + if(nReset == 1'b0) begin + dataCacheHi <= 8'h0; + end else begin + if(cpuAddr >= 24'h3FA700 && cpuAddr < 24'h400000 && cpuRnW == 1'b0) begin + dataCacheHi <= cpuData[15:8]; + end + end + end + + // when cpu addresses the framebuffer, save low byte + always @(negedge ncpuUDS or negedge nReset) begin + if(nReset == 1'b0) begin + dataCacheLo <= 8'h0; + end else begin + if(cpuAddr >= 24'h3FA700 && cpuAddr < 24'h400000 && cpuRnW == 1'b0) begin + dataCacheLo <= cpuData[7:0]; + end + end + end + + // set pending flags for cpu accesses & clear when that cycle comes back around + always @(negedge pixClock or negedge nReset) begin + if(nReset == 1'b0) begin + pendWriteLo <= 1'b0; + pendWriteHi <= 1'b0; + end else begin + if(cpuAddr >= 24'h3FA700 && cpuAddr < 24'h400000 && cpuRnW == 1'b0) begin + if(ncpuUDS == 1'b0) begin + pendWriteHi <= 1'b1; + end + if(ncpuLDS == 1'b0) begin + pendWriteLo <= 1'b1; + end + end else begin + if(sequence == 3'h1) begin + pendWriteLo <= 1'b0; + end + if(sequence == 3'h2) begin + pendWriteHi <= 1'b0; + end + end + end + end + + always_comb begin + vramAddr[12:1] <= addrCache[12:1]; + if(pendWriteLo == 1'b1 && sequence == 3'h1) begin + vramAddr[0] <= 1'b0; + nvramWE <= 1'b0; + vramData <= dataCacheLo; + end else if(pendWriteHi == 1'b1 && sequence = 3'h2) begin + vramAddr[0] <= 1'b1; + nvramWE <= 1'b0; + vramData <= dataCacheHi; + end else begin + vramAddr[0] <= 1'b0; + nvramWE <= 1'b1; + vramData <= 8'bZ; + end + end +endmodule \ No newline at end of file diff --git a/primitives/primitives.sv b/primitives/primitives.sv new file mode 100644 index 0000000..7c145a3 --- /dev/null +++ b/primitives/primitives.sv @@ -0,0 +1,76 @@ +/****************************************************************************** + * SE-VGA + * Primitives + * techav + * 2021-04-06 + ****************************************************************************** + * Basic modules to be used elsewhere + *****************************************************************************/ + +// basic d-flipflop +module dff ( + input wire nReset, + input wire clk, + input wire d, + output reg q +); + always @(posedge clock or negedge nReset) begin + if(nReset == 1'b0) begin + q <= 1'b0; + end else begin + q <= d; + end + end +endmodule + +// basic 8-bit mux +module mux8 ( + input logic [7:0] inA, + input logic [7:0] inB, + input wire select, + output logic [7:0] out +); + always_comb begin + if(select == 1'b0) begin + out <= inA; + end else begin + out <= inB; + end + end +endmodule + +// basic 8-to-1 mux +module mux8x1 ( + input logic[7:0] in, + input logic[2:0] select, + output wire out +); + always_comb begin + out <= in[select]; + end +endmodule + +// basic 8-bit PISO shift register +module piso8 ( + input wire nReset, + input wire clk, + input wire load, + input logic [7:0] parIn, + output wire out +); + + logic [7:0] muxIns; + logic [7:0] muxOuts; + + mux8 loader(muxIns[7:0],parIn[7:0],load,muxOuts[7:0]); + dff u0(nReset,clk,muxOuts[0],muxIns[1]); + dff u1(nReset,clk,muxOuts[1],muxIns[2]); + dff u2(nReset,clk,muxOuts[2],muxIns[3]); + dff u3(nReset,clk,muxOuts[3],muxIns[4]); + dff u4(nReset,clk,muxOuts[4],muxIns[5]); + dff u5(nReset,clk,muxOuts[5],muxIns[6]); + dff u6(nReset,clk,muxOuts[6],muxIns[7]); + dff u7(nReset,clk,muxOuts[7],muxIns[0]); + + out <= muxIns[0]; +endmodule \ No newline at end of file diff --git a/se-vga.sv b/se-vga.sv new file mode 100644 index 0000000..3b0c830 --- /dev/null +++ b/se-vga.sv @@ -0,0 +1,47 @@ +/****************************************************************************** + * SE-VGA + * Top-level module + * techav + * 2021-04-06 + ****************************************************************************** + * Pulls together all the smaller modules to form the SE-VGA adapter + *****************************************************************************/ + +module design sevga ( + input wire nReset, // System reset signal + input wire pixClk, // 25.175MHz pixel clock + output wire nhSync, // HSync signal + output wire nvSync, // VSync signal + output wire vidOut, // 1-bit Monochrome video signal + + output logic [12: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 + + 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 wire cpuClk // CPU Clock +); + +logic [9:0] hCount; +logic [9:0] vCount; +wire hActive; +wire hSEActive; + +logic [7:0] vidVramData; +logic [12:0] vidVramAddr; + + +// link module that generates all our timing signals +vgagen vgatiming(nReset,pixClk,hCount,hActive,hSEActive,nhSync,vCount,vActive,vSEActive,nvSync); +// link module that fetches & outputs video data +vgaout vidvram(pixClock,nReset,hCount,vCount,hSEActive,vSEActive,vidVramData,vidVramAddr,nvramOE,vidOut); +// link module that handles cpu writes + + +endmodule \ No newline at end of file diff --git a/vgacount.sv b/vgacount.sv new file mode 100644 index 0000000..9d120c4 --- /dev/null +++ b/vgacount.sv @@ -0,0 +1,67 @@ +/****************************************************************************** + * SE-VGA + * VGA signal counter + * techav + * 2021-04-06 + ****************************************************************************** + * Low-level VGA signal counter + *****************************************************************************/ + +module vgacount ( + input wire nReset, // system reset signal + input wire clock, // counter increment clock + output logic [9:0] count, // count output + output wire nSync, // sync pulse + output wire activeVid, // active video signal + output wire activeSE // secondary active video signal (SE) +); + +parameter COUNTMAX=800, + SYNCBEGIN=592, + SYNCEND=688, + ACTBEGIN=576, + ACTEND=736, + SEACTBEGIN=512; + +logic [9:0] counter; + +// primary counter +always @(negedge clock or negedge nReset) begin + if(nReset == 1'b0) begin + counter <= 10'h0; + end else begin + if (counter < COUNTMAX) begin + counter <= counter + 10'h1; + end else begin + counter <= 10'h0; + end + end +end + + +// combinatorial logic derived from the counters +always_comb begin + // output the count signals + count <= counter; + + // Sync pulse + if(hCount >= SYNCBEGIN && hCount < SYNCEND) begin + nhSync <= 1'b0; + end else begin + nhSync <= 1'b1; + end + + if(hCount >= ACTBEGIN && hCount < ACTEND) begin + hActive <= 1'b0; + end else begin + hActive <= 1'b1; + end + + if(hCount >= SEACTBEGIN) begin + hSEActive <= 1'b0; + end else begin + hSEActive <= 1'b1; + end +end + +endmodule \ No newline at end of file diff --git a/vgagen.sv b/vgagen.sv new file mode 100644 index 0000000..7ae371a --- /dev/null +++ b/vgagen.sv @@ -0,0 +1,28 @@ +/****************************************************************************** + * SE-VGA + * VGA timing generator + * techav + * 2021-04-06 + ****************************************************************************** + * Generates VGA timing signals & counters + *****************************************************************************/ + +`include "vgacount.sv" + +module vgagen ( + input wire nReset, // master reset signal + input wire pixClk, // 25.175MHz pixel clock + output logic [9:0] hCount, // horizontal pixel count + output wire hActive, // horizontal VGA active video signal + output wire hSEActive, // horizontal SE active video signal + output wire nhSync, // horizontal sync pulse signal + output logic [9:0] vCount, // vertical line count + output wire vActive, // vertical VGA active video signal + output wire vSEActive, // vertical SE active video signal + output wire nvSync // vertical sync pulse signal +); + +vgacount #(800,592,688,576,736,512) hoz(nReset,pixClk,hCount,nhSync,hActive,hSEActive); +vgacount #(525,421,423,411,456,342) ver(nReset,nhSync,vCount,nvSync,vActive,vSEActive); + +endmodule \ No newline at end of file diff --git a/vgaout.sv b/vgaout.sv new file mode 100644 index 0000000..e08b1d7 --- /dev/null +++ b/vgaout.sv @@ -0,0 +1,70 @@ +/****************************************************************************** + * SE-VGA + * VGA video output + * techav + * 2021-04-06 + ****************************************************************************** + * Fetches video data from VRAM and shifts out + *****************************************************************************/ + +`include "primitives.sv" + +module vgaout ( + input wire pixClock, + input wire nReset, + input logic [9:0] hCount, + input logic [9:0] vCount, + input wire hSEActive, + input wire vSEActive, + inout logic [7:0] vramData, + output logic [12:0] vramAddr, + output wire nvramOE, + output wire vidOut +); + +reg [7:0] rVid; +wire vidMuxOut; +wire vidActive; // combined active video signal + +mux8x1 vidOutMux(rVid[7:0],hCount[2:0],vidMuxOut); + +// latch incoming vram data on rising clock and sequence 7 +always @(posedge pixClock or negedge nReset) begin + if(nReset == 1'b0) begin + rVid <= 8'h0; + end else begin + if(hCount[2:0] == 3'b7) begin + rVid <= vramData; + end + end +end + +always_comb begin + // combined video active signal + if(hSEActive == 1'b1 && vSEActive == 1'b1) begin + vidActive <= 1'b1; + end else begin + vidActive <= 1'b0; + end + + // video data output + if(vidActive == 1'b1) begin + vidOut <= vidMuxOut; + end else begin + vidOut <= 1'b0; + end + + // vram read signal + if(vidActive == 1'b1 && hCount[2:0] == 3'b7) begin + nvramOE <= 1'b0; + end else begin + nvramOE <= 1'b1; + end + + // vram address signals + // these will be mux'd with cpu addresses externally + vramAddr[12:6] <= vCount[6:0]; + vramAddr[5:0] <= hCount[8:3]; +end + +endmodule \ No newline at end of file