mirror of https://github.com/zxdos/zxuno.git
358 lines
11 KiB
Verilog
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
|