1
0
mirror of https://github.com/Gehstock/Mist_FPGA.git synced 2026-01-17 16:43:22 +00:00
2019-07-23 21:42:55 +02:00

477 lines
16 KiB
Systemverilog

/* Atari on an FPGA
Masters of Engineering Project
Cornell University, 2007
Daniel Beer
TIA.v
Redesign of the Atari TIA chip. Provides the Atari with video generation,
sound generation and I/O.
*/
`timescale 1ns / 1ps
`include "tia.vh"
module TIA(A, // Address bus input
Din, // Data bus input
Dout, // Data bus output
CS_n, // Active low chip select input
CS, // Chip select input
R_W_n, // Active low read/write input
RDY, // CPU ready output
MASTERCLK, // 3.58 Mhz pixel clock input
CLK2, // 1.19 Mhz bus clock input
idump_in, // Dumped I/O
Ilatch, // Latched I/O
HSYNC, // Video horizontal sync output
HBLANK, // Video horizontal blank output
VSYNC, // Video vertical sync output
VBLANK, // Video vertical sync output
COLOROUT, // Indexed color output
RES_n, // Active low reset input
AUD0, //audio pin 0
AUD1, //audio pin 1
audv0, //audio volume for use with external xformer module
audv1); //audio volume for use with external xformer module
input [5:0] A;
input [7:0] Din;
output [7:0] Dout;
input [2:0] CS_n;
input CS;
input R_W_n;
output RDY;
input MASTERCLK;
input CLK2;
input [1:0] Ilatch;
input [3:0] idump_in;
output HSYNC, HBLANK;
output VSYNC, VBLANK;
output [7:0] COLOROUT;
input RES_n;
output AUD0, AUD1;
output reg [3:0] audv0, audv1;
// Data output register
reg [7:0] Dout;
// Video control signal registers
wire HSYNC;
reg VSYNC, VBLANK;
// Horizontal pixel counter
reg [7:0] hCount;
reg [3:0] hCountReset;
reg clk_30;
reg [7:0] clk_30_count;
wire [3:0] Idump;
// Pixel counter update
always @(posedge MASTERCLK)
begin
// Reset operation
if (~RES_n) begin
hCount <= 8'd0;
hCountReset[3:1] <= 3'd0;
clk_30 <= 0;
clk_30_count <= 0;
latchedInputs <= 2'b11;
end
else begin
if (inputLatchReset)
latchedInputs <= 2'b11;
else
latchedInputs <= latchedInputs & Ilatch;
if (clk_30_count == 57) begin
clk_30 <= ~clk_30;
clk_30_count <= 0;
end else begin
clk_30_count <= clk_30_count + 1;
end
// Increment the count and reset if necessary
if ((hCountReset[3]) ||(hCount == 8'd227))
hCount <= 8'd0;
else
hCount <= hCount + 8'd1;
// Software resets are delayed by three cycles
hCountReset[3:1] <= hCountReset[2:0];
end
end
assign HSYNC = (hCount >= 8'd20) && (hCount < 8'd36);
assign HBLANK = (hCount < 8'd68);
// Screen object registers
// These registers are set by the software and used to generate pixels
reg [7:0] player0Pos, player1Pos, missile0Pos, missile1Pos, ballPos;
reg [4:0] player0Size, player1Size;
reg [7:0] player0Color, player1Color, ballColor, pfColor, bgColor;
reg [3:0] player0Motion, player1Motion, missile0Motion, missile1Motion,
ballMotion;
reg missile0Enable, missile1Enable, ballEnable, R_ballEnable;
reg [1:0] ballSize;
reg [19:0] pfGraphic;
reg [7:0] player0Graphic, player1Graphic;
reg [7:0] R_player0Graphic, R_player1Graphic;
reg pfReflect, player0Reflect, player1Reflect;
reg prioCtrl;
reg pfColorCtrl;
reg [14:0] collisionLatch;
reg missile0Lock, missile1Lock;
reg player0VertDelay, player1VertDelay, ballVertDelay;
reg [3:0] audc0, audc1;
reg [4:0] audf0, audf1;
// Pixel number calculation
wire [7:0] pixelNum;
//audio control
audio audio_ctrl(.AUDC0(audc0),
.AUDC1(audc1),
.AUDF0(audf0),
.AUDF1(audf1),
.CLK_30(clk_30), //30khz clock
.AUD0(AUD0),
.AUD1(AUD1));
assign pixelNum = (hCount >= 8'd68) ? (hCount - 8'd68) : 8'd227;
// Pixel tests. For each pixel and screen object, a test is done based on the
// screen objects register to determine if the screen object should show on that
// pixel. The results of all the tests are fed into logic to pick which displayed
// object has priority and color the pixel the color of that object.
// Playfield pixel test
wire [5:0] pfPixelNum;
wire pfPixelOn, pfLeftPixelVal, pfRightPixelVal;
assign pfPixelNum = pixelNum[7:2];
assign pfLeftPixelVal = pfGraphic[pfPixelNum];
assign pfRightPixelVal = (pfReflect == 1'b0)? pfGraphic[pfPixelNum - 6'd20]:
pfGraphic[6'd39 - pfPixelNum];
assign pfPixelOn = (pfPixelNum < 6'd20)? pfLeftPixelVal : pfRightPixelVal;
// Player 0 sprite pixel test
wire pl0PixelOn;
wire [7:0] pl0Mask, pl0MaskDel;
assign pl0MaskDel = (player0VertDelay)? R_player0Graphic : player0Graphic;
assign pl0Mask = (!player0Reflect)? pl0MaskDel : {pl0MaskDel[0], pl0MaskDel[1],
pl0MaskDel[2], pl0MaskDel[3],
pl0MaskDel[4], pl0MaskDel[5],
pl0MaskDel[6], pl0MaskDel[7]};
objPixelOn player0_test(pixelNum, player0Pos, player0Size[2:0], pl0Mask, pl0PixelOn);
// Player 1 sprite pixel test
wire pl1PixelOn;
wire [7:0] pl1Mask, pl1MaskDel;
assign pl1MaskDel = (player1VertDelay)? R_player1Graphic : player1Graphic;
assign pl1Mask = (!player1Reflect)? pl1MaskDel : {pl1MaskDel[0], pl1MaskDel[1],
pl1MaskDel[2], pl1MaskDel[3],
pl1MaskDel[4], pl1MaskDel[5],
pl1MaskDel[6], pl1MaskDel[7]};
objPixelOn player1_test(pixelNum, player1Pos, player1Size[2:0], pl1Mask, pl1PixelOn);
// Missile 0 pixel test
wire mis0PixelOn, mis0PixelOut;
wire [7:0] mis0ActualPos;
reg [7:0] mis0Mask;
always @(player0Size)
begin
case(player0Size[4:3])
2'd0: mis0Mask <= 8'h01;
2'd1: mis0Mask <= 8'h03;
2'd2: mis0Mask <= 8'h0F;
2'd3: mis0Mask <= 8'hFF;
endcase
end
assign mis0ActualPos = (missile0Lock)? player0Pos : missile0Pos;
objPixelOn missile0_test(pixelNum, mis0ActualPos, player0Size[2:0], mis0Mask, mis0PixelOut);
assign mis0PixelOn = mis0PixelOut && missile0Enable;
// Missile 1 pixel test
wire mis1PixelOn, mis1PixelOut;
wire [7:0] mis1ActualPos;
reg [7:0] mis1Mask;
always @(player1Size)
begin
case(player1Size[4:3])
2'd0: mis1Mask <= 8'h01;
2'd1: mis1Mask <= 8'h03;
2'd2: mis1Mask <= 8'h0F;
2'd3: mis1Mask <= 8'hFF;
endcase
end
assign mis1ActualPos = (missile1Lock)? player1Pos : missile1Pos;
objPixelOn missile1_test(pixelNum, mis1ActualPos, player1Size[2:0], mis1Mask, mis1PixelOut);
assign mis1PixelOn = mis1PixelOut && missile1Enable;
// Ball pixel test
wire ballPixelOut, ballPixelOn, ballEnableDel;
reg [7:0] ballMask;
always @(ballSize)
begin
case(ballSize)
2'd0: ballMask <= 8'h01;
2'd1: ballMask <= 8'h03;
2'd2: ballMask <= 8'h0F;
2'd3: ballMask <= 8'hFF;
endcase
end
objPixelOn ball_test(pixelNum, ballPos, 3'd0, ballMask, ballPixelOut);
assign ballEnableDel = ((ballVertDelay)? R_ballEnable : ballEnable);
assign ballPixelOn = ballPixelOut && ballEnableDel;
// Playfield color selection
// The programmer can select a unique color for the playfield or have it match
// the player's sprites colors
reg [7:0] pfActualColor;
always @(pfColorCtrl, pfColor, player0Color, player1Color, pfPixelNum)
begin
if (pfColorCtrl)
begin
if (pfPixelNum < 6'd20)
pfActualColor <= player0Color;
else
pfActualColor <= player1Color;
end
else
pfActualColor <= pfColor;
end
// Final pixel color selection
reg [7:0] pixelColor;
assign COLOROUT = (HBLANK)? 8'b0 : pixelColor;
// This combinational logic uses a priority encoder like structure to select
// the highest priority screen object and color the pixel.
always @(prioCtrl, pfPixelOn, pl0PixelOn, pl1PixelOn, mis0PixelOn, mis1PixelOn,
ballPixelOn, pfActualColor, player0Color, player1Color, bgColor)
begin
// Show the playfield behind the players
if (!prioCtrl)
begin
if (pl0PixelOn || mis0PixelOn)
pixelColor <= player0Color;
else if (pl1PixelOn || mis1PixelOn)
pixelColor <= player1Color;
else if (pfPixelOn)
pixelColor <= pfActualColor;
else
pixelColor <= bgColor;
end
// Otherwise, show the playfield in front of the players
else begin
if (pfPixelOn)
pixelColor <= pfActualColor;
else if (pl0PixelOn || mis0PixelOn)
pixelColor <= player0Color;
else if (pl1PixelOn || mis1PixelOn)
pixelColor <= player1Color;
else
pixelColor <= bgColor;
end
end
// Collision register and latching update
wire [14:0] collisions;
reg collisionLatchReset;
assign collisions = {pl0PixelOn && pl1PixelOn, mis0PixelOn && mis1PixelOn,
ballPixelOn && pfPixelOn,
mis1PixelOn && pfPixelOn, mis1PixelOn && ballPixelOn,
mis0PixelOn && pfPixelOn, mis0PixelOn && ballPixelOn,
pl1PixelOn && pfPixelOn, pl1PixelOn && ballPixelOn,
pl0PixelOn && pfPixelOn, pl0PixelOn && ballPixelOn,
mis1PixelOn && pl0PixelOn, mis1PixelOn && pl1PixelOn,
mis0PixelOn && pl1PixelOn, mis0PixelOn && pl0PixelOn};
always @(posedge MASTERCLK, posedge collisionLatchReset)
begin
if (collisionLatchReset)
collisionLatch <= 15'b000000000000000;
else
collisionLatch <= collisionLatch | collisions;
end
// WSYNC logic
// When a WSYNC is signalled by the programmer, the CPU ready line is lowered
// until the end of a scanline
reg wSync, wSyncReset;
always @(hCount, wSyncReset)
begin
if (hCount == 8'd0)
wSync <= 1'b0;
else if (wSyncReset && hCount > 8'd2)
wSync <= 1'b1;
end
assign RDY = ~wSync;
// Latched input registers and update
wire [1:0] latchedInputsValue;
reg inputLatchEnabled;
reg inputLatchReset;
reg [1:0] latchedInputs;
/*always_ff @(Ilatch, inputLatchReset)
begin
if (inputLatchReset)
latchedInputs <= 2'b11;
else
latchedInputs <= latchedInputs & Ilatch;
end*/
assign latchedInputsValue = (inputLatchEnabled)? latchedInputs : Ilatch;
// Dumped input registers update
reg inputDumpEnabled;
assign Idump = (inputDumpEnabled)? 4'b0000 : idump_in;
// Software operations
always @(posedge CLK2)
begin
// Reset operation
if (~RES_n) begin
inputLatchReset <= 1'b0;
collisionLatchReset <= 1'b0;
hCountReset[0] <= 1'b0;
wSyncReset <= 1'b0;
Dout <= 8'b00000000;
end
// If the chip is enabled, execute an operation
else if (CS) begin
// Software reset signals
inputLatchReset <= ({R_W_n, A[5:0]} == `VBLANK && Din[6] && !inputLatchEnabled);
collisionLatchReset <= ({R_W_n, A[5:0]} == `CXCLR);
hCountReset[0] <= ({R_W_n, A[5:0]} == `RSYNC);
wSyncReset <= ({R_W_n, A[5:0]} == `WSYNC) && !wSync;
case({R_W_n, A[5:0]})
// Collision latch reads
`CXM0P, `CXM0P_7800: Dout <= {collisionLatch[1:0],6'b000000};
`CXM1P, `CXM1P_7800: Dout <= {collisionLatch[3:2],6'b000000};
`CXP0FB, `CXP0FB_7800: Dout <= {collisionLatch[5:4],6'b000000};
`CXP1FB, `CXP1FB_7800: Dout <= {collisionLatch[7:6],6'b000000};
`CXM0FB, `CXM0FB_7800: Dout <= {collisionLatch[9:8],6'b000000};
`CXM1FB, `CXM1FB_7800: Dout <= {collisionLatch[11:10],6'b000000};
`CXBLPF, `CXBLPF_7800: Dout <= {collisionLatch[12],7'b0000000};
`CXPPMM, `CXPPMM_7800: Dout <= {collisionLatch[14:13],6'b000000};
// I/O reads
`INPT0, `INPT0_7800: Dout <= {Idump[0], 7'b0000000};
`INPT1, `INPT1_7800: Dout <= {Idump[1], 7'b0000000};
`INPT2, `INPT2_7800: Dout <= {Idump[2], 7'b0000000};
`INPT3, `INPT3_7800: Dout <= {Idump[3], 7'b0000000};
`INPT4, `INPT4_7800: Dout <= {latchedInputsValue[0], 7'b0000000};
`INPT5, `INPT5_7800: Dout <= {latchedInputsValue[1], 7'b0000000};
// Video signals
`VSYNC: VSYNC <= Din[1];
`VBLANK: begin
inputLatchEnabled <= Din[6];
inputDumpEnabled <= Din[7];
VBLANK <= Din[1];
end
`WSYNC:;
`RSYNC:;
// Screen object register access
`NUSIZ0: player0Size <= {Din[5:4],Din[2:0]};
`NUSIZ1: player1Size <= {Din[5:4],Din[2:0]};
`COLUP0: player0Color <= Din;
`COLUP1: player1Color <= Din;
`COLUPF: pfColor <= Din;
`COLUBK: bgColor <= Din;
`CTRLPF: begin
pfReflect <= Din[0];
pfColorCtrl <= Din[1];
prioCtrl <= Din[2];
ballSize <= Din[5:4];
end
`REFP0: player0Reflect <= Din[3];
`REFP1: player1Reflect <= Din[3];
`PF0: pfGraphic[3:0] <= Din[7:4];
`PF1: pfGraphic[11:4] <= {Din[0], Din[1], Din[2], Din[3],
Din[4], Din[5], Din[6], Din[7]};
`PF2: pfGraphic[19:12] <= Din[7:0];
`RESP0: player0Pos <= pixelNum;
`RESP1: player1Pos <= pixelNum;
`RESM0: missile0Pos <= pixelNum;
`RESM1: missile1Pos <= pixelNum;
`RESBL: ballPos <= pixelNum;
// Audio controls
`AUDC0: audc0 <= Din[3:0];
`AUDC1: audc1 <= Din[3:0];
`AUDF0: audf0 <= Din[4:0];
`AUDF1: audf1 <= Din[4:0];
`AUDV0: audv0 <= Din[3:0];
`AUDV1: audv1 <= Din[3:0];
// Screen object register access
`GRP0: begin
player0Graphic <= {Din[0], Din[1], Din[2], Din[3],
Din[4], Din[5], Din[6], Din[7]};
R_player1Graphic <= player1Graphic;
end
`GRP1: begin
player1Graphic <= {Din[0], Din[1], Din[2], Din[3],
Din[4], Din[5], Din[6], Din[7]};
R_player0Graphic <= player0Graphic;
R_ballEnable <= ballEnable;
end
`ENAM0: missile0Enable <= Din[1];
`ENAM1: missile1Enable <= Din[1];
`ENABL: ballEnable <= Din[1];
`HMP0: player0Motion <= Din[7:4];
`HMP1: player1Motion <= Din[7:4];
`HMM0: missile0Motion <= Din[7:4];
`HMM1: missile1Motion <= Din[7:4];
`HMBL: ballMotion <= Din[7:4];
`VDELP0: player0VertDelay <= Din[0];
`VDELP1: player1VertDelay <= Din[0];
`VDELBL: ballVertDelay <= Din[0];
`RESMP0: missile0Lock <= Din[1];
`RESMP1: missile1Lock <= Din[1];
// Strobed line that initiates an object move
`HMOVE: begin
player0Pos <= player0Pos - {{4{player0Motion[3]}},
player0Motion[3:0]};
player1Pos <= player1Pos - {{4{player1Motion[3]}},
player1Motion[3:0]};
missile0Pos <= missile0Pos - {{4{missile0Motion[3]}},
missile0Motion[3:0]};
missile1Pos <= missile1Pos - {{4{missile1Motion[3]}},
missile1Motion[3:0]};
ballPos <= ballPos - {{4{ballMotion[3]}},ballMotion[3:0]};
end
// Motion register clear
`HMCLR: begin
player0Motion <= Din[7:4];
player1Motion <= Din[7:4];
missile0Motion <= Din[7:4];
missile1Motion <= Din[7:4];
ballMotion <= Din[7:4];
end
`CXCLR:;
default: Dout <= 8'b00000000;
endcase
end
// If the chip is not enabled, do nothing
else begin
inputLatchReset <= 1'b0;
collisionLatchReset <= 1'b0;
hCountReset[0] <= 1'b0;
wSyncReset <= 1'b0;
Dout <= 8'b00000000;
end
end
endmodule
// objPixelOn module
// Checks the pixel number against a stretched and possibly duplicated version of the
// object.
module objPixelOn(pixelNum, objPos, objSize, objMask, pixelOn);
input [7:0] pixelNum, objPos, objMask;
input [2:0] objSize;
output pixelOn;
wire [7:0] objIndex;
wire [8:0] objByteIndex;
wire objMaskOn, objPosOn;
reg objSizeOn;
reg [2:0] objMaskSel;
assign objIndex = pixelNum - objPos - 8'd1;
assign objByteIndex = 9'b1 << (objIndex[7:3]);
always @(objSize, objByteIndex)
begin
case (objSize)
3'd0: objSizeOn <= (objByteIndex & 9'b00000001) != 0;
3'd1: objSizeOn <= (objByteIndex & 9'b00000101) != 0;
3'd2: objSizeOn <= (objByteIndex & 9'b00010001) != 0;
3'd3: objSizeOn <= (objByteIndex & 9'b00010101) != 0;
3'd4: objSizeOn <= (objByteIndex & 9'b10000001) != 0;
3'd5: objSizeOn <= (objByteIndex & 9'b00000011) != 0;
3'd6: objSizeOn <= (objByteIndex & 9'b10010001) != 0;
3'd7: objSizeOn <= (objByteIndex & 9'b00001111) != 0;
endcase
end
always @(objSize, objIndex)
begin
case (objSize)
3'd5: objMaskSel <= objIndex[3:1];
3'd7: objMaskSel <= objIndex[4:2];
default: objMaskSel <= objIndex[2:0];
endcase
end
assign objMaskOn = objMask[objMaskSel];
assign objPosOn = (pixelNum > objPos) && ({1'b0, pixelNum} <= {1'b0, objPos} + 9'd72);
assign pixelOn = objSizeOn && objMaskOn && objPosOn;
endmodule