mirror of
https://github.com/Gehstock/Mist_FPGA.git
synced 2026-01-17 16:43:22 +00:00
477 lines
16 KiB
Systemverilog
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
|