zxuno-git/cores/NES/src/cpu.v

358 lines
11 KiB
Verilog

// Copyright (c) 2012-2013 Ludvig Strigeus
// This program is GPL Licensed. See COPYING for the full license.
`include "MicroCode.v"
module MyAddSub(input [7:0] A,B,
input CI,ADD,
output [7:0] S,
output CO,OFL);
wire C0,C1,C2,C3,C4,C5,C6;
wire C6O;
wire [7:0] I = A ^ B ^ {8{~ADD}};
MUXCY_L MUXCY_L0 (.LO(C0),.CI(CI),.DI(A[0]),.S(I[0]) );
MUXCY_L MUXCY_L1 (.LO(C1),.CI(C0),.DI(A[1]),.S(I[1]) );
MUXCY_L MUXCY_L2 (.LO(C2),.CI(C1),.DI(A[2]),.S(I[2]) );
MUXCY_L MUXCY_L3 (.LO(C3),.CI(C2),.DI(A[3]),.S(I[3]) );
MUXCY_L MUXCY_L4 (.LO(C4),.CI(C3),.DI(A[4]),.S(I[4]) );
MUXCY_L MUXCY_L5 (.LO(C5),.CI(C4),.DI(A[5]),.S(I[5]) );
MUXCY_D MUXCY_D6 (.LO(C6),.O(C6O),.CI(C5),.DI(A[6]),.S(I[6]) );
MUXCY MUXCY_7 (.O(CO),.CI(C6),.DI(A[7]),.S(I[7]) );
XORCY XORCY0 (.O(S[0]),.CI(CI),.LI(I[0]));
XORCY XORCY1 (.O(S[1]),.CI(C0),.LI(I[1]));
XORCY XORCY2 (.O(S[2]),.CI(C1),.LI(I[2]));
XORCY XORCY3 (.O(S[3]),.CI(C2),.LI(I[3]));
XORCY XORCY4 (.O(S[4]),.CI(C3),.LI(I[4]));
XORCY XORCY5 (.O(S[5]),.CI(C4),.LI(I[5]));
XORCY XORCY6 (.O(S[6]),.CI(C5),.LI(I[6]));
XORCY XORCY7 (.O(S[7]),.CI(C6),.LI(I[7]));
XOR2 X1(.O(OFL),.I0(C6O),.I1(CO));
endmodule
module NewAlu(input [10:0] OP, // ALU Operation
input [7:0] A, // Registers input
input [7:0] X, // ""
input [7:0] Y, // ""
input [7:0] S, // ""
input [7:0] M, // Secondary input to ALU
input [7:0] T, // Secondary input to ALU
// -- Flags Input
input CI, // Carry In
input VI, // Overflow In
// -- Flags output
output reg CO, // Carry out
output reg VO, // Overflow out
output reg SO, // Sign out
output reg ZO, // zero out
// -- Result output
output reg [7:0] Result,// Result out
output reg [7:0] IntR); // Intermediate result out
// Crack the ALU Operation
wire [1:0] o_left_input, o_right_input;
wire [2:0] o_first_op, o_second_op;
wire o_fc;
assign {o_left_input, o_right_input, o_first_op, o_second_op, o_fc} = OP;
// Determine left, right inputs to Add/Sub ALU.
reg [7:0] L, R;
reg CR;
always begin
casez(o_left_input)
0: L = A;
1: L = Y;
2: L = X;
3: L = A & X;
endcase
casez(o_right_input)
0: R = M;
1: R = T;
2: R = L;
3: R = S;
endcase
CR = CI;
casez(o_first_op[2:1])
0: {CR, IntR} = {R, CI & o_first_op[0]}; // SHL, ROL
1: {IntR, CR} = {CI & o_first_op[0], R}; // SHR, ROR
2: IntR = R; // Passthrough
3: IntR = R + (o_first_op[0] ? 8'b1 : 8'b11111111); // INC/DEC
endcase
end
wire [7:0] AddR;
wire AddCO, AddVO;
MyAddSub addsub(.A(L),.B(IntR),.CI(o_second_op[0] ? CR : 1'b1),.ADD(!o_second_op[2]), .S(AddR), .CO(AddCO), .OFL(AddVO));
// Produce the output of the second stage.
always begin
casez(o_second_op)
0: {CO, Result} = {CR, L | IntR};
1: {CO, Result} = {CR, L & IntR};
2: {CO, Result} = {CR, L ^ IntR};
3, 6, 7: {CO, Result} = {AddCO, AddR};
4, 5: {CO, Result} = {CR, IntR};
endcase
// Final result
ZO = (Result == 0);
// Compute overflow flag
VO = VI;
casez(o_second_op)
1: if (!o_fc) VO = IntR[6]; // Set V to 6th bit for BIT
3: VO = AddVO; // ADC always sets V
7: if (o_fc) VO = AddVO; // Only SBC sets V.
endcase
// Compute sign flag. It's always the uppermost bit of the result,
// except for BIT that sets it to the 7th input bit
SO = (o_second_op == 1 && !o_fc) ? IntR[7] : Result[7];
end
endmodule
module AddressGenerator(input clk, input ce,
input [4:0] Operation,
input [1:0] MuxCtrl,
input [7:0] DataBus, T, X, Y,
output [15:0] AX,
output Carry);
// Actual contents of registers
reg [7:0] AL, AH;
// Last operation generated a carry?
reg SavedCarry;
assign AX = {AH, AL};
wire [2:0] ALCtrl = Operation[4:2];
wire [1:0] AHCtrl = Operation[1:0];
// Possible new value for AL.
wire [7:0] NewAL;
assign {Carry,NewAL} = {1'b0, (MuxCtrl[1] ? T : AL)} + {1'b0, (MuxCtrl[0] ? Y : X)};
// The other one
wire TmpVal = (!AHCtrl[1] | SavedCarry);
wire [7:0] TmpAdd = (AHCtrl[1] ? AH : AL) + {7'b0, TmpVal};
always @(posedge clk) if (ce) begin
SavedCarry <= Carry;
if (ALCtrl[2])
case(ALCtrl[1:0])
0: AL <= NewAL;
1: AL <= DataBus;
2: AL <= TmpAdd;
3: AL <= T;
endcase
case(AHCtrl[1:0])
0: AH <= AH;
1: AH <= 0;
2: AH <= TmpAdd;
3: AH <= DataBus;
endcase
end
endmodule
module ProgramCounter(input clk, input ce,
input [1:0] LoadPC,
input GotInterrupt,
input [7:0] DIN,
input [7:0] T,
output reg [15:0] PC, output JumpNoOverflow);
reg [15:0] NewPC;
assign JumpNoOverflow = ((PC[8] ^ NewPC[8]) == 0) & LoadPC[0] & LoadPC[1];
always begin
// Load PC Control
case (LoadPC)
0,1: NewPC = PC + {15'b0, (LoadPC[0] & ~GotInterrupt)};
2: NewPC = {DIN,T};
3: NewPC = PC + {{8{T[7]}},T};
endcase
end
always @(posedge clk)
if (ce)
PC <= NewPC;
endmodule
module CPU(input clk, input ce, input reset,
input [7:0] DIN,
input irq,
input nmi,
output reg [7:0] dout, output reg [15:0] aout,
output reg mr,
output reg mw);
reg [7:0] A, X, Y;
reg [7:0] SP, T, P;
reg [7:0] IR;
reg [2:0] State;
reg GotInterrupt;
reg IsResetInterrupt;
wire [15:0] PC;
reg JumpTaken;
wire JumpNoOverflow;
// De-multiplex microcode
wire [37:0] MicroCode;
wire [1:0] LoadSP = MicroCode[1:0]; // 7 LUT
wire [1:0] LoadPC = MicroCode[3:2]; // 12 LUT
wire [1:0] AddrBus = MicroCode[5:4]; // 18 LUT
wire [2:0] MemWrite = MicroCode[8:6]; // 10 LUT
wire [4:0] AddrCtrl = MicroCode[13:9];
wire FlagCtrl = MicroCode[14]; // RegWrite + FlagCtrl = 22 LUT
wire [1:0] LoadT = MicroCode[16:15]; // 13 LUT
wire [1:0] StateCtrl = MicroCode[18:17];
wire [2:0] FlagsCtrl = MicroCode[21:19];
wire [15:0] IrFlags = MicroCode[37:22];
// Load Instruction Register? Force BRK on Interrupt.
wire [7:0] NextIR = (State == 0) ? (GotInterrupt ? 8'd0 : DIN) : IR;
wire IsBranchCycle1 = (IR[4:0] == 5'b10000) && State[0];
// Compute next state.
reg [2:0] NextState;
always begin
case (StateCtrl)
0: NextState = State + 3'd1;
1: NextState = (AXCarry ? 3'd4 : 3'd5);
2: NextState = (IsBranchCycle1 && JumpTaken) ? 2 : 0; // Override next state if the branch is taken.
3: NextState = (JumpNoOverflow ? 3'd0 : 3'd4);
endcase
end
wire [15:0] AX;
wire AXCarry;
AddressGenerator addgen(clk, ce, AddrCtrl, {IrFlags[0], IrFlags[1]}, DIN, T, X, Y, AX, AXCarry);
// Microcode table has a 1 clock latency (block ram).
MicroCodeTable micro2(clk, ce, reset, NextIR, NextState, MicroCode);
wire [7:0] AluR;
wire [7:0] AluIntR;
wire CO, VO, SO,ZO;
NewAlu new_alu(IrFlags[15:5], A,X,Y,SP,DIN,T, P[0], P[6], CO, VO, SO, ZO, AluR, AluIntR);
// Registers
always @(posedge clk) if (reset) begin
A <= 0;
X <= 0;
Y <= 0;
end else if (ce) begin
if (FlagCtrl & IrFlags[2]) X <= AluR;
if (FlagCtrl & IrFlags[3]) A <= AluR;
if (FlagCtrl & IrFlags[4]) Y <= AluR;
end
// Program counter
ProgramCounter pc(clk, ce, LoadPC, GotInterrupt, DIN, T, PC, JumpNoOverflow);
reg IsNMIInterrupt;
reg LastNMI;
// NMI is triggered at any time, except during reset, or when we're in the middle of
// reading the vector address
wire turn_nmi_on = (AddrBus != 3) && !IsResetInterrupt && nmi && !LastNMI;
// Controls whether we'll remember the state in LastNMI
wire nmi_remembered = (AddrBus != 3) && !IsResetInterrupt ? nmi : LastNMI;
// NMI flag is cleared right after we read the vector address
wire turn_nmi_off = (AddrBus == 3) && (State[0] == 0);
// Controls whether IsNmiInterrupt will get set
wire nmi_active = turn_nmi_on ? 1 : turn_nmi_off ? 0 : IsNMIInterrupt;
always @(posedge clk) begin
if (reset) begin
IsNMIInterrupt <= 0;
LastNMI <= 0;
end else if (ce) begin
IsNMIInterrupt <= nmi_active;
LastNMI <= nmi_remembered;
end
end
// Generate outputs from module...
always begin
dout = 8'bX;
case (MemWrite[1:0])
'b00: dout = T;
'b01: dout = AluR;
'b10: dout = {P[7:6], 1'b1, !GotInterrupt, P[3:0]};
'b11: dout = State[0] ? PC[7:0] : PC[15:8];
endcase
mw = MemWrite[2] && !IsResetInterrupt; // Prevent writing while handling a reset
mr = !mw;
end
always begin
case (AddrBus)
0: aout = PC;
1: aout = AX;
2: aout = {8'h01, SP};
// irq/BRK vector FFFE
// nmi vector FFFA
// Reset vector FFFC
3: aout = {13'b1111_1111_1111_1, !IsNMIInterrupt, !IsResetInterrupt, ~State[0]};
endcase
end
always @(posedge clk) begin
if (reset) begin
// Reset runs the BRK instruction as usual.
State <= 0;
IR <= 0;
GotInterrupt <= 1;
IsResetInterrupt <= 1;
P <= 'h24;
SP <= 0;
T <= 0;
JumpTaken <= 0;
end else if (ce) begin
// Stack pointer control.
// The operand is an optimization that either
// returns -1,0,1 depending on LoadSP
case (LoadSP)
0,2,3: SP <= SP + { {7{LoadSP[0]}}, LoadSP[1] };
1: SP <= X;
endcase
// LoadT control
case (LoadT)
2: T <= DIN;
3: T <= AluIntR;
endcase
if (FlagCtrl) begin
case(FlagsCtrl)
0: P <= P; // No Op
1: {P[0], P[1], P[6], P[7]} <= {CO, ZO, VO, SO}; // ALU
2: P[2] <= 1; // BRK
3: P[6] <= 0; // CLV
4: {P[7:6],P[3:0]} <= {DIN[7:6], DIN[3:0]}; // RTI/PLP
5: P[0] <= IR[5]; // CLC/SEC
6: P[2] <= IR[5]; // CLI/SEI
7: P[3] <= IR[5]; // CLD/SED
endcase
end
// Compute if the jump is to be taken. Result is stored in a flipflop, so
// it won't be available until next cycle.
// NOTE: Using DIN here. DIN is IR at cycle 0. This means JumpTaken will
// contain the Jump status at cycle 1.
case (DIN[7:5])
0: JumpTaken <= ~P[7]; // BPL
1: JumpTaken <= P[7]; // BMI
2: JumpTaken <= ~P[6]; // BVC
3: JumpTaken <= P[6]; // BVS
4: JumpTaken <= ~P[0]; // BCC
5: JumpTaken <= P[0]; // BCS
6: JumpTaken <= ~P[1]; // BNE
7: JumpTaken <= P[1]; // BEQ
endcase
// Check the interrupt status on the last cycle of the current instruction,
// (or on cycle #1 of any branch instruction)
if (StateCtrl == 2'b10) begin
GotInterrupt <= (irq & ~P[2]) | nmi_active;
IsResetInterrupt <= 0;
end
IR <= NextIR;
State <= NextState;
end
end
endmodule