2019-04-22 14:09:33 +00:00
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
/* verilator lint_off CASEX */
|
|
|
|
/* verilator lint_off CASEINCOMPLETE */
|
|
|
|
/* verilator lint_off CASEOVERLAP */
|
|
|
|
/* verilator lint_off SYNCASYNCNET */
|
|
|
|
|
2019-04-22 14:09:33 +00:00
|
|
|
/*
|
|
|
|
* verilog model of 6502 CPU.
|
|
|
|
*
|
|
|
|
* (C) Arlet Ottens, <arlet@c-scape.nl>
|
|
|
|
*
|
2019-08-29 13:36:18 +00:00
|
|
|
* https://github.com/Arlet/verilog-6502/
|
|
|
|
*
|
2019-04-22 14:09:33 +00:00
|
|
|
* Feel free to use this code in any project (commercial or not), as long as you
|
|
|
|
* keep this message, and the copyright notice. This code is provided "as is",
|
|
|
|
* without any warranties of any kind.
|
|
|
|
*
|
|
|
|
* Note that not all 6502 interface signals are supported (yet). The goal
|
|
|
|
* is to create an Acorn Atom model, and the Atom didn't use all signals on
|
|
|
|
* the main board.
|
|
|
|
*
|
|
|
|
* The data bus is implemented as separate read/write buses. Combine them
|
|
|
|
* on the output pads if external memory is required.
|
2021-06-03 23:17:06 +00:00
|
|
|
*
|
|
|
|
* Also see: https://github.com/sehugg/mango_one
|
2019-04-22 14:09:33 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
module cpu6502( clk, reset, AB, DI, DO, WE, IRQ, NMI, RDY );
|
|
|
|
|
|
|
|
input clk; // CPU clock
|
|
|
|
input reset; // reset signal
|
|
|
|
output reg [15:0] AB; // address bus
|
|
|
|
input [7:0] DI; // data in, read bus
|
|
|
|
output [7:0] DO; // data out, write bus
|
|
|
|
output WE; // write enable
|
|
|
|
input IRQ; // interrupt request
|
|
|
|
input NMI; // non-maskable interrupt request
|
|
|
|
input RDY; // Ready signal. Pauses CPU when RDY=0
|
|
|
|
|
|
|
|
/*
|
|
|
|
* internal signals
|
|
|
|
*/
|
|
|
|
|
|
|
|
reg [15:0] PC; // Program Counter
|
|
|
|
reg [7:0] ABL; // Address Bus Register LSB
|
|
|
|
reg [7:0] ABH; // Address Bus Register MSB
|
|
|
|
wire [7:0] ADD; // Adder Hold Register (registered in ALU)
|
|
|
|
|
|
|
|
reg [7:0] DIHOLD; // Hold for Data In
|
|
|
|
reg DIHOLD_valid; //
|
|
|
|
wire [7:0] DIMUX; //
|
|
|
|
|
|
|
|
reg [7:0] IRHOLD; // Hold for Instruction register
|
|
|
|
reg IRHOLD_valid; // Valid instruction in IRHOLD
|
|
|
|
|
|
|
|
reg [7:0] AXYS[3:0]; // A, X, Y and S register file
|
|
|
|
|
|
|
|
reg C = 0; // carry flag (init at zero to avoid X's in ALU sim)
|
|
|
|
reg Z = 0; // zero flag
|
|
|
|
reg I = 0; // interrupt flag
|
|
|
|
reg D = 0; // decimal flag
|
|
|
|
reg V = 0; // overflow flag
|
|
|
|
reg N = 0; // negative flag
|
|
|
|
wire AZ; // ALU Zero flag
|
|
|
|
wire AV; // ALU overflow flag
|
|
|
|
wire AN; // ALU negative flag
|
|
|
|
wire HC; // ALU half carry
|
|
|
|
|
|
|
|
reg [7:0] AI; // ALU Input A
|
|
|
|
reg [7:0] BI; // ALU Input B
|
|
|
|
wire [7:0] DI; // Data In
|
|
|
|
wire [7:0] IR; // Instruction register
|
|
|
|
reg [7:0] DO; // Data Out
|
|
|
|
reg WE; // Write Enable
|
|
|
|
reg CI; // Carry In
|
|
|
|
wire CO; // Carry Out
|
|
|
|
wire [7:0] PCH = PC[15:8];
|
|
|
|
wire [7:0] PCL = PC[7:0];
|
|
|
|
|
|
|
|
reg NMI_edge = 0; // captured NMI edge
|
|
|
|
|
|
|
|
reg [1:0] regsel; // Select A, X, Y or S register
|
|
|
|
wire [7:0] regfile = AXYS[regsel]; // Selected register output
|
|
|
|
|
|
|
|
parameter
|
|
|
|
SEL_A = 2'd0,
|
|
|
|
SEL_S = 2'd1,
|
|
|
|
SEL_X = 2'd2,
|
|
|
|
SEL_Y = 2'd3;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* define some signals for watching in simulator output
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
`ifdef SIM
|
|
|
|
wire [7:0] A = AXYS[SEL_A]; // Accumulator
|
|
|
|
wire [7:0] X = AXYS[SEL_X]; // X register
|
|
|
|
wire [7:0] Y = AXYS[SEL_Y]; // Y register
|
|
|
|
wire [7:0] S = AXYS[SEL_S]; // Stack pointer
|
|
|
|
`endif
|
|
|
|
|
|
|
|
wire [7:0] P = { N, V, 2'b11, D, I, Z, C };
|
|
|
|
|
|
|
|
/*
|
|
|
|
* instruction decoder/sequencer
|
|
|
|
*/
|
|
|
|
|
|
|
|
reg [5:0] state;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* control signals
|
|
|
|
*/
|
|
|
|
|
|
|
|
reg PC_inc; // Increment PC
|
|
|
|
reg [15:0] PC_temp; // intermediate value of PC
|
|
|
|
|
|
|
|
reg [1:0] src_reg; // source register index
|
|
|
|
reg [1:0] dst_reg; // destination register index
|
|
|
|
|
|
|
|
reg index_y; // if set, then Y is index reg rather than X
|
|
|
|
reg load_reg; // loading a register (A, X, Y, S) in this instruction
|
|
|
|
reg inc; // increment
|
|
|
|
reg write_back; // set if memory is read/modified/written
|
|
|
|
reg load_only; // LDA/LDX/LDY instruction
|
|
|
|
reg store; // doing store (STA/STX/STY)
|
|
|
|
reg adc_sbc; // doing ADC/SBC
|
|
|
|
reg compare; // doing CMP/CPY/CPX
|
|
|
|
reg shift; // doing shift/rotate instruction
|
|
|
|
reg rotate; // doing rotate (no shift)
|
|
|
|
reg backwards; // backwards branch
|
|
|
|
reg cond_true; // branch condition is true
|
|
|
|
reg [2:0] cond_code; // condition code bits from instruction
|
|
|
|
reg shift_right; // Instruction ALU shift/rotate right
|
|
|
|
reg alu_shift_right; // Current cycle shift right enable
|
|
|
|
reg [3:0] op; // Main ALU operation for instruction
|
|
|
|
reg [3:0] alu_op; // Current cycle ALU operation
|
|
|
|
reg adc_bcd; // ALU should do BCD style carry
|
|
|
|
reg adj_bcd; // results should be BCD adjusted
|
|
|
|
|
|
|
|
/*
|
|
|
|
* some flip flops to remember we're doing special instructions. These
|
|
|
|
* get loaded at the DECODE state, and used later
|
|
|
|
*/
|
|
|
|
reg bit_ins; // doing BIT instruction
|
|
|
|
reg plp; // doing PLP instruction
|
|
|
|
reg php; // doing PHP instruction
|
|
|
|
reg clc; // clear carry
|
|
|
|
reg sec; // set carry
|
|
|
|
reg cld; // clear decimal
|
|
|
|
reg sed; // set decimal
|
|
|
|
reg cli; // clear interrupt
|
|
|
|
reg sei; // set interrupt
|
|
|
|
reg clv; // clear overflow
|
|
|
|
reg brk; // doing BRK
|
|
|
|
|
|
|
|
reg res; // in reset
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU operations
|
|
|
|
*/
|
|
|
|
|
|
|
|
parameter
|
|
|
|
OP_OR = 4'b1100,
|
|
|
|
OP_AND = 4'b1101,
|
|
|
|
OP_EOR = 4'b1110,
|
|
|
|
OP_ADD = 4'b0011,
|
|
|
|
OP_SUB = 4'b0111,
|
|
|
|
OP_ROL = 4'b1011,
|
|
|
|
OP_A = 4'b1111;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Microcode state machine. Basically, every addressing mode has its own
|
|
|
|
* path through the state machine. Additional information, such as the
|
|
|
|
* operation, source and destination registers are decoded in parallel, and
|
|
|
|
* kept in separate flops.
|
|
|
|
*/
|
|
|
|
|
|
|
|
parameter
|
|
|
|
ABS0 = 6'd0, // ABS - fetch LSB
|
|
|
|
ABS1 = 6'd1, // ABS - fetch MSB
|
|
|
|
ABSX0 = 6'd2, // ABS, X - fetch LSB and send to ALU (+X)
|
|
|
|
ABSX1 = 6'd3, // ABS, X - fetch MSB and send to ALU (+Carry)
|
|
|
|
ABSX2 = 6'd4, // ABS, X - Wait for ALU (only if needed)
|
|
|
|
BRA0 = 6'd5, // Branch - fetch offset and send to ALU (+PC[7:0])
|
|
|
|
BRA1 = 6'd6, // Branch - fetch opcode, and send PC[15:8] to ALU
|
|
|
|
BRA2 = 6'd7, // Branch - fetch opcode (if page boundary crossed)
|
|
|
|
BRK0 = 6'd8, // BRK/IRQ - push PCH, send S to ALU (-1)
|
|
|
|
BRK1 = 6'd9, // BRK/IRQ - push PCL, send S to ALU (-1)
|
|
|
|
BRK2 = 6'd10, // BRK/IRQ - push P, send S to ALU (-1)
|
|
|
|
BRK3 = 6'd11, // BRK/IRQ - write S, and fetch @ fffe
|
|
|
|
DECODE = 6'd12, // IR is valid, decode instruction, and write prev reg
|
|
|
|
FETCH = 6'd13, // fetch next opcode, and perform prev ALU op
|
|
|
|
INDX0 = 6'd14, // (ZP,X) - fetch ZP address, and send to ALU (+X)
|
|
|
|
INDX1 = 6'd15, // (ZP,X) - fetch LSB at ZP+X, calculate ZP+X+1
|
|
|
|
INDX2 = 6'd16, // (ZP,X) - fetch MSB at ZP+X+1
|
|
|
|
INDX3 = 6'd17, // (ZP,X) - fetch data
|
|
|
|
INDY0 = 6'd18, // (ZP),Y - fetch ZP address, and send ZP to ALU (+1)
|
|
|
|
INDY1 = 6'd19, // (ZP),Y - fetch at ZP+1, and send LSB to ALU (+Y)
|
|
|
|
INDY2 = 6'd20, // (ZP),Y - fetch data, and send MSB to ALU (+Carry)
|
|
|
|
INDY3 = 6'd21, // (ZP),Y) - fetch data (if page boundary crossed)
|
|
|
|
JMP0 = 6'd22, // JMP - fetch PCL and hold
|
|
|
|
JMP1 = 6'd23, // JMP - fetch PCH
|
|
|
|
JMPI0 = 6'd24, // JMP IND - fetch LSB and send to ALU for delay (+0)
|
|
|
|
JMPI1 = 6'd25, // JMP IND - fetch MSB, proceed with JMP0 state
|
|
|
|
JSR0 = 6'd26, // JSR - push PCH, save LSB, send S to ALU (-1)
|
|
|
|
JSR1 = 6'd27, // JSR - push PCL, send S to ALU (-1)
|
|
|
|
JSR2 = 6'd28, // JSR - write S
|
|
|
|
JSR3 = 6'd29, // JSR - fetch MSB
|
|
|
|
PULL0 = 6'd30, // PLP/PLA - save next op in IRHOLD, send S to ALU (+1)
|
|
|
|
PULL1 = 6'd31, // PLP/PLA - fetch data from stack, write S
|
|
|
|
PULL2 = 6'd32, // PLP/PLA - prefetch op, but don't increment PC
|
|
|
|
PUSH0 = 6'd33, // PHP/PHA - send A to ALU (+0)
|
|
|
|
PUSH1 = 6'd34, // PHP/PHA - write A/P, send S to ALU (-1)
|
|
|
|
READ = 6'd35, // Read memory for read/modify/write (INC, DEC, shift)
|
|
|
|
REG = 6'd36, // Read register for reg-reg transfers
|
|
|
|
RTI0 = 6'd37, // RTI - send S to ALU (+1)
|
|
|
|
RTI1 = 6'd38, // RTI - read P from stack
|
|
|
|
RTI2 = 6'd39, // RTI - read PCL from stack
|
|
|
|
RTI3 = 6'd40, // RTI - read PCH from stack
|
|
|
|
RTI4 = 6'd41, // RTI - read PCH from stack
|
|
|
|
RTS0 = 6'd42, // RTS - send S to ALU (+1)
|
|
|
|
RTS1 = 6'd43, // RTS - read PCL from stack
|
|
|
|
RTS2 = 6'd44, // RTS - write PCL to ALU, read PCH
|
|
|
|
RTS3 = 6'd45, // RTS - load PC and increment
|
|
|
|
WRITE = 6'd46, // Write memory for read/modify/write
|
|
|
|
ZP0 = 6'd47, // Z-page - fetch ZP address
|
|
|
|
ZPX0 = 6'd48, // ZP, X - fetch ZP, and send to ALU (+X)
|
|
|
|
ZPX1 = 6'd49; // ZP, X - load from memory
|
|
|
|
|
|
|
|
`ifdef SIM
|
|
|
|
|
|
|
|
/*
|
|
|
|
* easy to read names in simulator output
|
|
|
|
*/
|
|
|
|
reg [8*6-1:0] statename;
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
DECODE: statename = "DECODE";
|
|
|
|
REG: statename = "REG";
|
|
|
|
ZP0: statename = "ZP0";
|
|
|
|
ZPX0: statename = "ZPX0";
|
|
|
|
ZPX1: statename = "ZPX1";
|
|
|
|
ABS0: statename = "ABS0";
|
|
|
|
ABS1: statename = "ABS1";
|
|
|
|
ABSX0: statename = "ABSX0";
|
|
|
|
ABSX1: statename = "ABSX1";
|
|
|
|
ABSX2: statename = "ABSX2";
|
|
|
|
INDX0: statename = "INDX0";
|
|
|
|
INDX1: statename = "INDX1";
|
|
|
|
INDX2: statename = "INDX2";
|
|
|
|
INDX3: statename = "INDX3";
|
|
|
|
INDY0: statename = "INDY0";
|
|
|
|
INDY1: statename = "INDY1";
|
|
|
|
INDY2: statename = "INDY2";
|
|
|
|
INDY3: statename = "INDY3";
|
|
|
|
READ: statename = "READ";
|
|
|
|
WRITE: statename = "WRITE";
|
|
|
|
FETCH: statename = "FETCH";
|
|
|
|
PUSH0: statename = "PUSH0";
|
|
|
|
PUSH1: statename = "PUSH1";
|
|
|
|
PULL0: statename = "PULL0";
|
|
|
|
PULL1: statename = "PULL1";
|
|
|
|
PULL2: statename = "PULL2";
|
|
|
|
JSR0: statename = "JSR0";
|
|
|
|
JSR1: statename = "JSR1";
|
|
|
|
JSR2: statename = "JSR2";
|
|
|
|
JSR3: statename = "JSR3";
|
|
|
|
RTI0: statename = "RTI0";
|
|
|
|
RTI1: statename = "RTI1";
|
|
|
|
RTI2: statename = "RTI2";
|
|
|
|
RTI3: statename = "RTI3";
|
|
|
|
RTI4: statename = "RTI4";
|
|
|
|
RTS0: statename = "RTS0";
|
|
|
|
RTS1: statename = "RTS1";
|
|
|
|
RTS2: statename = "RTS2";
|
|
|
|
RTS3: statename = "RTS3";
|
|
|
|
BRK0: statename = "BRK0";
|
|
|
|
BRK1: statename = "BRK1";
|
|
|
|
BRK2: statename = "BRK2";
|
|
|
|
BRK3: statename = "BRK3";
|
|
|
|
BRA0: statename = "BRA0";
|
|
|
|
BRA1: statename = "BRA1";
|
|
|
|
BRA2: statename = "BRA2";
|
|
|
|
JMP0: statename = "JMP0";
|
|
|
|
JMP1: statename = "JMP1";
|
|
|
|
JMPI0: statename = "JMPI0";
|
|
|
|
JMPI1: statename = "JMPI1";
|
|
|
|
endcase
|
|
|
|
|
|
|
|
//always @( PC )
|
|
|
|
// $display( "%t, PC:%04x IR:%02x A:%02x X:%02x Y:%02x S:%02x C:%d Z:%d V:%d N:%d P:%02x", $time, PC, IR, A, X, Y, S, C, Z, V, N, P );
|
|
|
|
|
|
|
|
`endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Program Counter Increment/Load. First calculate the base value in
|
|
|
|
* PC_temp.
|
|
|
|
*/
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
DECODE: if( (~I & IRQ) | NMI_edge )
|
|
|
|
PC_temp = { ABH, ABL };
|
|
|
|
else
|
|
|
|
PC_temp = PC;
|
|
|
|
|
|
|
|
|
|
|
|
JMP1,
|
|
|
|
JMPI1,
|
|
|
|
JSR3,
|
|
|
|
RTS3,
|
|
|
|
RTI4: PC_temp = { DIMUX, ADD };
|
|
|
|
|
|
|
|
BRA1: PC_temp = { ABH, ADD };
|
|
|
|
|
|
|
|
BRA2: PC_temp = { ADD, PCL };
|
|
|
|
|
|
|
|
BRK2: PC_temp = res ? 16'hfffc :
|
|
|
|
NMI_edge ? 16'hfffa : 16'hfffe;
|
|
|
|
|
|
|
|
default: PC_temp = PC;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine wether we need PC_temp, or PC_temp + 1
|
|
|
|
*/
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
DECODE: if( (~I & IRQ) | NMI_edge )
|
|
|
|
PC_inc = 0;
|
|
|
|
else
|
|
|
|
PC_inc = 1;
|
|
|
|
|
|
|
|
ABS0,
|
|
|
|
ABSX0,
|
|
|
|
FETCH,
|
|
|
|
BRA0,
|
|
|
|
BRA2,
|
|
|
|
BRK3,
|
|
|
|
JMPI1,
|
|
|
|
JMP1,
|
|
|
|
RTI4,
|
|
|
|
RTS3: PC_inc = 1;
|
|
|
|
|
|
|
|
BRA1: PC_inc = CO ^~ backwards;
|
|
|
|
|
|
|
|
default: PC_inc = 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set new PC
|
|
|
|
*/
|
|
|
|
always @(posedge clk)
|
|
|
|
if( RDY )
|
2021-07-07 16:06:22 +00:00
|
|
|
PC <= PC_temp + {15'b0, PC_inc};
|
2019-04-22 14:09:33 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Address Generator
|
|
|
|
*/
|
|
|
|
|
|
|
|
parameter
|
|
|
|
ZEROPAGE = 8'h00,
|
|
|
|
STACKPAGE = 8'h01;
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
ABSX1,
|
|
|
|
INDX3,
|
|
|
|
INDY2,
|
|
|
|
JMP1,
|
|
|
|
JMPI1,
|
|
|
|
RTI4,
|
|
|
|
ABS1: AB = { DIMUX, ADD };
|
|
|
|
|
|
|
|
BRA2,
|
|
|
|
INDY3,
|
|
|
|
ABSX2: AB = { ADD, ABL };
|
|
|
|
|
|
|
|
BRA1: AB = { ABH, ADD };
|
|
|
|
|
|
|
|
JSR0,
|
|
|
|
PUSH1,
|
|
|
|
RTS0,
|
|
|
|
RTI0,
|
|
|
|
BRK0: AB = { STACKPAGE, regfile };
|
|
|
|
|
|
|
|
BRK1,
|
|
|
|
JSR1,
|
|
|
|
PULL1,
|
|
|
|
RTS1,
|
|
|
|
RTS2,
|
|
|
|
RTI1,
|
|
|
|
RTI2,
|
|
|
|
RTI3,
|
|
|
|
BRK2: AB = { STACKPAGE, ADD };
|
|
|
|
|
|
|
|
INDY1,
|
|
|
|
INDX1,
|
|
|
|
ZPX1,
|
|
|
|
INDX2: AB = { ZEROPAGE, ADD };
|
|
|
|
|
|
|
|
ZP0,
|
|
|
|
INDY0: AB = { ZEROPAGE, DIMUX };
|
|
|
|
|
|
|
|
REG,
|
|
|
|
READ,
|
|
|
|
WRITE: AB = { ABH, ABL };
|
|
|
|
|
|
|
|
default: AB = PC;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ABH/ABL pair is used for registering previous address bus state.
|
|
|
|
* This can be used to keep the current address, freeing up the original
|
|
|
|
* source of the address, such as the ALU or DI.
|
|
|
|
*/
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state != PUSH0 && state != PUSH1 && RDY &&
|
|
|
|
state != PULL0 && state != PULL1 && state != PULL2 )
|
|
|
|
begin
|
|
|
|
ABL <= AB[7:0];
|
|
|
|
ABH <= AB[15:8];
|
|
|
|
end
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Data Out MUX
|
|
|
|
*/
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
WRITE: DO = ADD;
|
|
|
|
|
|
|
|
JSR0,
|
|
|
|
BRK0: DO = PCH;
|
|
|
|
|
|
|
|
JSR1,
|
|
|
|
BRK1: DO = PCL;
|
|
|
|
|
|
|
|
PUSH1: DO = php ? P : ADD;
|
|
|
|
|
|
|
|
BRK2: DO = (IRQ | NMI_edge) ? (P & 8'b1110_1111) : P;
|
|
|
|
|
|
|
|
default: DO = regfile;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write Enable Generator
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
BRK0, // writing to stack or memory
|
|
|
|
BRK1,
|
|
|
|
BRK2,
|
|
|
|
JSR0,
|
|
|
|
JSR1,
|
|
|
|
PUSH1,
|
|
|
|
WRITE: WE = 1;
|
|
|
|
|
|
|
|
INDX3, // only if doing a STA, STX or STY
|
|
|
|
INDY3,
|
|
|
|
ABSX2,
|
|
|
|
ABS1,
|
|
|
|
ZPX1,
|
|
|
|
ZP0: WE = store;
|
|
|
|
|
|
|
|
default: WE = 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* register file, contains A, X, Y and S (stack pointer) registers. At each
|
|
|
|
* cycle only 1 of those registers needs to be accessed, so they combined
|
|
|
|
* in a small memory, saving resources.
|
|
|
|
*/
|
|
|
|
|
|
|
|
reg write_register; // set when register file is written
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
DECODE: write_register = load_reg & ~plp;
|
|
|
|
|
|
|
|
PULL1,
|
|
|
|
RTS2,
|
|
|
|
RTI3,
|
|
|
|
BRK3,
|
|
|
|
JSR0,
|
|
|
|
JSR2 : write_register = 1;
|
|
|
|
|
|
|
|
default: write_register = 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* BCD adjust logic
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
adj_bcd <= adc_sbc & D; // '1' when doing a BCD instruction
|
|
|
|
|
|
|
|
reg [3:0] ADJL;
|
|
|
|
reg [3:0] ADJH;
|
|
|
|
|
|
|
|
// adjustment term to be added to ADD[3:0] based on the following
|
|
|
|
// adj_bcd: '1' if doing ADC/SBC with D=1
|
|
|
|
// adc_bcd: '1' if doing ADC with D=1
|
|
|
|
// HC : half carry bit from ALU
|
|
|
|
always @* begin
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( {adj_bcd, adc_bcd, HC} )
|
|
|
|
3'b0xx: ADJL = 4'd0; // no BCD instruction
|
2019-04-22 14:09:33 +00:00
|
|
|
3'b100: ADJL = 4'd10; // SBC, and digital borrow
|
|
|
|
3'b101: ADJL = 4'd0; // SBC, but no borrow
|
|
|
|
3'b110: ADJL = 4'd0; // ADC, but no carry
|
|
|
|
3'b111: ADJL = 4'd6; // ADC, and decimal/digital carry
|
|
|
|
endcase
|
|
|
|
end
|
|
|
|
|
|
|
|
// adjustment term to be added to ADD[7:4] based on the following
|
|
|
|
// adj_bcd: '1' if doing ADC/SBC with D=1
|
|
|
|
// adc_bcd: '1' if doing ADC with D=1
|
|
|
|
// CO : carry out bit from ALU
|
|
|
|
always @* begin
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( {adj_bcd, adc_bcd, CO} )
|
|
|
|
3'b0xx: ADJH = 4'd0; // no BCD instruction
|
2019-04-22 14:09:33 +00:00
|
|
|
3'b100: ADJH = 4'd10; // SBC, and digital borrow
|
|
|
|
3'b101: ADJH = 4'd0; // SBC, but no borrow
|
|
|
|
3'b110: ADJH = 4'd0; // ADC, but no carry
|
|
|
|
3'b111: ADJH = 4'd6; // ADC, and decimal/digital carry
|
|
|
|
endcase
|
|
|
|
end
|
|
|
|
|
|
|
|
/*
|
|
|
|
* write to a register. Usually this is the (BCD corrected) output of the
|
|
|
|
* ALU, but in case of the JSR0 we use the S register to temporarily store
|
|
|
|
* the PCL. This is possible, because the S register itself is stored in
|
|
|
|
* the ALU during those cycles.
|
|
|
|
*/
|
|
|
|
always @(posedge clk)
|
|
|
|
if( write_register & RDY )
|
|
|
|
AXYS[regsel] <= (state == JSR0) ? DIMUX : { ADD[7:4] + ADJH, ADD[3:0] + ADJL };
|
|
|
|
|
|
|
|
/*
|
|
|
|
* register select logic. This determines which of the A, X, Y or
|
|
|
|
* S registers will be accessed.
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
INDY1,
|
|
|
|
INDX0,
|
|
|
|
ZPX0,
|
|
|
|
ABSX0 : regsel = index_y ? SEL_Y : SEL_X;
|
|
|
|
|
|
|
|
|
|
|
|
DECODE : regsel = dst_reg;
|
|
|
|
|
|
|
|
BRK0,
|
|
|
|
BRK3,
|
|
|
|
JSR0,
|
|
|
|
JSR2,
|
|
|
|
PULL0,
|
|
|
|
PULL1,
|
|
|
|
PUSH1,
|
|
|
|
RTI0,
|
|
|
|
RTI3,
|
|
|
|
RTS0,
|
|
|
|
RTS2 : regsel = SEL_S;
|
|
|
|
|
|
|
|
default: regsel = src_reg;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU
|
|
|
|
*/
|
|
|
|
|
|
|
|
ALU ALU( .clk(clk),
|
|
|
|
.op(alu_op),
|
|
|
|
.right(alu_shift_right),
|
|
|
|
.AI(AI),
|
|
|
|
.BI(BI),
|
|
|
|
.CI(CI),
|
|
|
|
.BCD(adc_bcd & (state == FETCH)),
|
|
|
|
.CO(CO),
|
|
|
|
.OUT(ADD),
|
|
|
|
.V(AV),
|
|
|
|
.Z(AZ),
|
|
|
|
.N(AN),
|
|
|
|
.HC(HC),
|
|
|
|
.RDY(RDY) );
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Select current ALU operation
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
READ: alu_op = op;
|
|
|
|
|
|
|
|
BRA1: alu_op = backwards ? OP_SUB : OP_ADD;
|
|
|
|
|
|
|
|
FETCH,
|
|
|
|
REG : alu_op = op;
|
|
|
|
|
|
|
|
DECODE,
|
|
|
|
ABS1: alu_op = 4'bx;
|
|
|
|
|
|
|
|
PUSH1,
|
|
|
|
BRK0,
|
|
|
|
BRK1,
|
|
|
|
BRK2,
|
|
|
|
JSR0,
|
|
|
|
JSR1: alu_op = OP_SUB;
|
|
|
|
|
|
|
|
default: alu_op = OP_ADD;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine shift right signal to ALU
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
if( state == FETCH || state == REG || state == READ )
|
|
|
|
alu_shift_right = shift_right;
|
|
|
|
else
|
|
|
|
alu_shift_right = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sign extend branch offset.
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( RDY )
|
|
|
|
backwards <= DIMUX[7];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU A Input MUX
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
JSR1,
|
|
|
|
RTS1,
|
|
|
|
RTI1,
|
|
|
|
RTI2,
|
|
|
|
BRK1,
|
|
|
|
BRK2,
|
|
|
|
INDX1: AI = ADD;
|
|
|
|
|
|
|
|
REG,
|
|
|
|
ZPX0,
|
|
|
|
INDX0,
|
|
|
|
ABSX0,
|
|
|
|
RTI0,
|
|
|
|
RTS0,
|
|
|
|
JSR0,
|
|
|
|
JSR2,
|
|
|
|
BRK0,
|
|
|
|
PULL0,
|
|
|
|
INDY1,
|
|
|
|
PUSH0,
|
|
|
|
PUSH1: AI = regfile;
|
|
|
|
|
|
|
|
BRA0,
|
|
|
|
READ: AI = DIMUX;
|
|
|
|
|
|
|
|
BRA1: AI = ABH; // don't use PCH in case we're
|
|
|
|
|
|
|
|
FETCH: AI = load_only ? 0 : regfile;
|
|
|
|
|
|
|
|
DECODE,
|
|
|
|
ABS1: AI = 8'hxx; // don't care
|
|
|
|
|
|
|
|
default: AI = 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU B Input mux
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
BRA1,
|
|
|
|
RTS1,
|
|
|
|
RTI0,
|
|
|
|
RTI1,
|
|
|
|
RTI2,
|
|
|
|
INDX1,
|
|
|
|
READ,
|
|
|
|
REG,
|
|
|
|
JSR0,
|
|
|
|
JSR1,
|
|
|
|
JSR2,
|
|
|
|
BRK0,
|
|
|
|
BRK1,
|
|
|
|
BRK2,
|
|
|
|
PUSH0,
|
|
|
|
PUSH1,
|
|
|
|
PULL0,
|
|
|
|
RTS0: BI = 8'h00;
|
|
|
|
|
|
|
|
BRA0: BI = PCL;
|
|
|
|
|
|
|
|
DECODE,
|
|
|
|
ABS1: BI = 8'hxx;
|
|
|
|
|
|
|
|
default: BI = DIMUX;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU CI (carry in) mux
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( state )
|
|
|
|
INDY2,
|
|
|
|
BRA1,
|
|
|
|
ABSX1: CI = CO;
|
|
|
|
|
|
|
|
DECODE,
|
|
|
|
ABS1: CI = 1'bx;
|
|
|
|
|
|
|
|
READ,
|
|
|
|
REG: CI = rotate ? C :
|
|
|
|
shift ? 0 : inc;
|
|
|
|
|
|
|
|
FETCH: CI = rotate ? C :
|
|
|
|
compare ? 1 :
|
|
|
|
(shift | load_only) ? 0 : C;
|
|
|
|
|
|
|
|
PULL0,
|
|
|
|
RTI0,
|
|
|
|
RTI1,
|
|
|
|
RTI2,
|
|
|
|
RTS0,
|
|
|
|
RTS1,
|
|
|
|
INDY0,
|
|
|
|
INDX1: CI = 1;
|
|
|
|
|
|
|
|
default: CI = 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Processor Status Register update
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update C flag when doing ADC/SBC, shift/rotate, compare
|
|
|
|
*/
|
|
|
|
always @(posedge clk )
|
|
|
|
if( shift && state == WRITE )
|
|
|
|
C <= CO;
|
|
|
|
else if( state == RTI2 )
|
|
|
|
C <= DIMUX[0];
|
|
|
|
else if( ~write_back && state == DECODE ) begin
|
|
|
|
if( adc_sbc | shift | compare )
|
|
|
|
C <= CO;
|
|
|
|
else if( plp )
|
|
|
|
C <= ADD[0];
|
|
|
|
else begin
|
|
|
|
if( sec ) C <= 1;
|
|
|
|
if( clc ) C <= 0;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update Z, N flags when writing A, X, Y, Memory, or when doing compare
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == WRITE )
|
|
|
|
Z <= AZ;
|
|
|
|
else if( state == RTI2 )
|
|
|
|
Z <= DIMUX[1];
|
|
|
|
else if( state == DECODE ) begin
|
|
|
|
if( plp )
|
|
|
|
Z <= ADD[1];
|
|
|
|
else if( (load_reg & (regsel != SEL_S)) | compare | bit_ins )
|
|
|
|
Z <= AZ;
|
|
|
|
end
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == WRITE )
|
|
|
|
N <= AN;
|
|
|
|
else if( state == RTI2 )
|
|
|
|
N <= DIMUX[7];
|
|
|
|
else if( state == DECODE ) begin
|
|
|
|
if( plp )
|
|
|
|
N <= ADD[7];
|
|
|
|
else if( (load_reg & (regsel != SEL_S)) | compare )
|
|
|
|
N <= AN;
|
|
|
|
end else if( state == FETCH && bit_ins )
|
|
|
|
N <= DIMUX[7];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update I flag
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == BRK3 )
|
|
|
|
I <= 1;
|
|
|
|
else if( state == RTI2 )
|
|
|
|
I <= DIMUX[2];
|
|
|
|
else if( state == REG ) begin
|
|
|
|
if( sei ) I <= 1;
|
|
|
|
if( cli ) I <= 0;
|
|
|
|
end else if( state == DECODE )
|
|
|
|
if( plp ) I <= ADD[2];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update D flag
|
|
|
|
*/
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == RTI2 )
|
|
|
|
D <= DIMUX[3];
|
|
|
|
else if( state == DECODE ) begin
|
|
|
|
if( sed ) D <= 1;
|
|
|
|
if( cld ) D <= 0;
|
|
|
|
if( plp ) D <= ADD[3];
|
|
|
|
end
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update V flag
|
|
|
|
*/
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == RTI2 )
|
|
|
|
V <= DIMUX[6];
|
|
|
|
else if( state == DECODE ) begin
|
|
|
|
if( adc_sbc ) V <= AV;
|
|
|
|
if( clv ) V <= 0;
|
|
|
|
if( plp ) V <= ADD[6];
|
|
|
|
end else if( state == FETCH && bit_ins )
|
|
|
|
V <= DIMUX[6];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Instruction decoder
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* IR register/mux. Hold previous DI value in IRHOLD in PULL0 and PUSH0
|
|
|
|
* states. In these states, the IR has been prefetched, and there is no
|
|
|
|
* time to read the IR again before the next decode.
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( reset )
|
|
|
|
IRHOLD_valid <= 0;
|
|
|
|
else if( RDY ) begin
|
|
|
|
if( state == PULL0 || state == PUSH0 ) begin
|
|
|
|
IRHOLD <= DIMUX;
|
|
|
|
IRHOLD_valid <= 1;
|
|
|
|
end else if( state == DECODE )
|
|
|
|
IRHOLD_valid <= 0;
|
|
|
|
end
|
|
|
|
|
|
|
|
assign IR = (IRQ & ~I) | NMI_edge ? 8'h00 :
|
|
|
|
IRHOLD_valid ? IRHOLD : DIMUX;
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( RDY )
|
|
|
|
DIHOLD <= DI;
|
|
|
|
|
|
|
|
assign DIMUX = ~RDY ? DIHOLD : DI;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
|
2019-04-22 14:09:33 +00:00
|
|
|
/*
|
|
|
|
* Microcode state machine
|
|
|
|
*/
|
2019-08-29 13:36:18 +00:00
|
|
|
always @(posedge clk or posedge reset)
|
2019-04-22 14:09:33 +00:00
|
|
|
if( reset )
|
|
|
|
state <= BRK0;
|
|
|
|
else if( RDY ) case( state )
|
|
|
|
DECODE :
|
2019-08-29 13:36:18 +00:00
|
|
|
casex ( IR )
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b0000_0000: state <= BRK0;
|
|
|
|
8'b0010_0000: state <= JSR0;
|
|
|
|
8'b0010_1100: state <= ABS0; // BIT abs
|
|
|
|
8'b0100_0000: state <= RTI0; //
|
|
|
|
8'b0100_1100: state <= JMP0;
|
|
|
|
8'b0110_0000: state <= RTS0;
|
|
|
|
8'b0110_1100: state <= JMPI0;
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b0x00_1000: state <= PUSH0;
|
|
|
|
8'b0x10_1000: state <= PULL0;
|
|
|
|
8'b0xx1_1000: state <= REG; // CLC, SEC, CLI, SEI
|
|
|
|
8'b1xx0_00x0: state <= FETCH; // IMM
|
|
|
|
8'b1xx0_1100: state <= ABS0; // X/Y abs
|
|
|
|
8'b1xxx_1000: state <= REG; // DEY, TYA, ...
|
|
|
|
8'bxxx0_0001: state <= INDX0;
|
|
|
|
8'bxxx0_01xx: state <= ZP0;
|
|
|
|
8'bxxx0_1001: state <= FETCH; // IMM
|
|
|
|
8'bxxx0_1101: state <= ABS0; // even E column
|
|
|
|
8'bxxx0_1110: state <= ABS0; // even E column
|
|
|
|
8'bxxx1_0000: state <= BRA0; // odd 0 column
|
|
|
|
8'bxxx1_0001: state <= INDY0; // odd 1 column
|
|
|
|
8'bxxx1_01xx: state <= ZPX0; // odd 4,5,6,7 columns
|
|
|
|
8'bxxx1_1001: state <= ABSX0; // odd 9 column
|
|
|
|
8'bxxx1_11xx: state <= ABSX0; // odd C, D, E, F columns
|
|
|
|
8'bxxxx_1010: state <= REG; // <shift> A, TXA, ... NOP
|
2019-04-22 14:09:33 +00:00
|
|
|
endcase
|
|
|
|
|
|
|
|
ZP0 : state <= write_back ? READ : FETCH;
|
|
|
|
|
|
|
|
ZPX0 : state <= ZPX1;
|
|
|
|
ZPX1 : state <= write_back ? READ : FETCH;
|
|
|
|
|
|
|
|
ABS0 : state <= ABS1;
|
|
|
|
ABS1 : state <= write_back ? READ : FETCH;
|
|
|
|
|
|
|
|
ABSX0 : state <= ABSX1;
|
|
|
|
ABSX1 : state <= (CO | store | write_back) ? ABSX2 : FETCH;
|
|
|
|
ABSX2 : state <= write_back ? READ : FETCH;
|
|
|
|
|
|
|
|
INDX0 : state <= INDX1;
|
|
|
|
INDX1 : state <= INDX2;
|
|
|
|
INDX2 : state <= INDX3;
|
|
|
|
INDX3 : state <= FETCH;
|
|
|
|
|
|
|
|
INDY0 : state <= INDY1;
|
|
|
|
INDY1 : state <= INDY2;
|
|
|
|
INDY2 : state <= (CO | store) ? INDY3 : FETCH;
|
|
|
|
INDY3 : state <= FETCH;
|
|
|
|
|
|
|
|
READ : state <= WRITE;
|
|
|
|
WRITE : state <= FETCH;
|
|
|
|
FETCH : state <= DECODE;
|
|
|
|
|
|
|
|
REG : state <= DECODE;
|
|
|
|
|
|
|
|
PUSH0 : state <= PUSH1;
|
|
|
|
PUSH1 : state <= DECODE;
|
|
|
|
|
|
|
|
PULL0 : state <= PULL1;
|
|
|
|
PULL1 : state <= PULL2;
|
|
|
|
PULL2 : state <= DECODE;
|
|
|
|
|
|
|
|
JSR0 : state <= JSR1;
|
|
|
|
JSR1 : state <= JSR2;
|
|
|
|
JSR2 : state <= JSR3;
|
|
|
|
JSR3 : state <= FETCH;
|
|
|
|
|
|
|
|
RTI0 : state <= RTI1;
|
|
|
|
RTI1 : state <= RTI2;
|
|
|
|
RTI2 : state <= RTI3;
|
|
|
|
RTI3 : state <= RTI4;
|
|
|
|
RTI4 : state <= DECODE;
|
|
|
|
|
|
|
|
RTS0 : state <= RTS1;
|
|
|
|
RTS1 : state <= RTS2;
|
|
|
|
RTS2 : state <= RTS3;
|
|
|
|
RTS3 : state <= FETCH;
|
|
|
|
|
|
|
|
BRA0 : state <= cond_true ? BRA1 : DECODE;
|
|
|
|
BRA1 : state <= (CO ^ backwards) ? BRA2 : DECODE;
|
|
|
|
BRA2 : state <= DECODE;
|
|
|
|
|
|
|
|
JMP0 : state <= JMP1;
|
|
|
|
JMP1 : state <= DECODE;
|
|
|
|
|
|
|
|
JMPI0 : state <= JMPI1;
|
|
|
|
JMPI1 : state <= JMP0;
|
|
|
|
|
|
|
|
BRK0 : state <= BRK1;
|
|
|
|
BRK1 : state <= BRK2;
|
|
|
|
BRK2 : state <= BRK3;
|
|
|
|
BRK3 : state <= JMP0;
|
|
|
|
|
|
|
|
endcase
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Additional control signals
|
|
|
|
*/
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( reset )
|
|
|
|
res <= 1;
|
|
|
|
else if( state == DECODE )
|
|
|
|
res <= 0;
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b0xx01010, // ASLA, ROLA, LSRA, RORA
|
|
|
|
8'b0xxxxx01, // ORA, AND, EOR, ADC
|
|
|
|
8'b100x10x0, // DEY, TYA, TXA, TXS
|
|
|
|
8'b1010xxx0, // LDA/LDX/LDY
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b10111010, // TSX
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b1011x1x0, // LDX/LDY
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b11001010, // DEX
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b1x1xxx01, // LDA, SBC
|
|
|
|
8'bxxx01000: // DEY, TAY, INY, INX
|
2019-04-22 14:09:33 +00:00
|
|
|
load_reg <= 1;
|
|
|
|
|
|
|
|
default: load_reg <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b1110_1000, // INX
|
|
|
|
8'b1100_1010, // DEX
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b101x_xx10: // LDX, TAX, TSX
|
2019-04-22 14:09:33 +00:00
|
|
|
dst_reg <= SEL_X;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b0x00_1000, // PHP, PHA
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b1001_1010: // TXS
|
|
|
|
dst_reg <= SEL_S;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b1x00_1000, // DEY, DEX
|
|
|
|
8'b101x_x100, // LDY
|
|
|
|
8'b1010_x000: // LDY #imm, TAY
|
2019-04-22 14:09:33 +00:00
|
|
|
dst_reg <= SEL_Y;
|
|
|
|
|
|
|
|
default: dst_reg <= SEL_A;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b1011_1010: // TSX
|
|
|
|
src_reg <= SEL_S;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b100x_x110, // STX
|
|
|
|
8'b100x_1x10, // TXA, TXS
|
|
|
|
8'b1110_xx00, // INX, CPX
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b1100_1010: // DEX
|
|
|
|
src_reg <= SEL_X;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b100x_x100, // STY
|
2019-04-22 14:09:33 +00:00
|
|
|
8'b1001_1000, // TYA
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b1100_xx00, // CPY
|
|
|
|
8'b1x00_1000: // DEY, INY
|
2019-04-22 14:09:33 +00:00
|
|
|
src_reg <= SEL_Y;
|
|
|
|
|
|
|
|
default: src_reg <= SEL_A;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'bxxx1_0001, // INDY
|
|
|
|
8'b10x1_x110, // LDX/STX zpg/abs, Y
|
|
|
|
8'bxxxx_1001: // abs, Y
|
2019-04-22 14:09:33 +00:00
|
|
|
index_y <= 1;
|
|
|
|
|
|
|
|
default: index_y <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b100x_x1x0, // STX, STY
|
|
|
|
8'b100x_xx01: // STA
|
2019-04-22 14:09:33 +00:00
|
|
|
store <= 1;
|
|
|
|
|
|
|
|
default: store <= 0;
|
|
|
|
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b0xxx_x110, // ASL, ROL, LSR, ROR
|
|
|
|
8'b11xx_x110: // DEC/INC
|
2019-04-22 14:09:33 +00:00
|
|
|
write_back <= 1;
|
|
|
|
|
|
|
|
default: write_back <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b101x_xxxx: // LDA, LDX, LDY
|
2019-04-22 14:09:33 +00:00
|
|
|
load_only <= 1;
|
|
|
|
default: load_only <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b111x_x110, // INC
|
|
|
|
8'b11x0_1000: // INX, INY
|
2019-04-22 14:09:33 +00:00
|
|
|
inc <= 1;
|
|
|
|
|
|
|
|
default: inc <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( (state == DECODE || state == BRK0) && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'bx11x_xx01: // SBC, ADC
|
2019-04-22 14:09:33 +00:00
|
|
|
adc_sbc <= 1;
|
|
|
|
|
|
|
|
default: adc_sbc <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( (state == DECODE || state == BRK0) && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b011x_xx01: // ADC
|
2019-04-22 14:09:33 +00:00
|
|
|
adc_bcd <= D;
|
|
|
|
|
|
|
|
default: adc_bcd <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b0xxx_x110, // ASL, ROL, LSR, ROR (abs, absx, zpg, zpgx)
|
|
|
|
8'b0xxx_1010: // ASL, ROL, LSR, ROR (acc)
|
2019-04-22 14:09:33 +00:00
|
|
|
shift <= 1;
|
|
|
|
|
|
|
|
default: shift <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b11x0_0x00, // CPX, CPY (imm/zp)
|
|
|
|
8'b11x0_1100, // CPX, CPY (abs)
|
|
|
|
8'b110x_xx01: // CMP
|
2019-04-22 14:09:33 +00:00
|
|
|
compare <= 1;
|
|
|
|
|
|
|
|
default: compare <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b01xx_xx10: // ROR, LSR
|
2019-04-22 14:09:33 +00:00
|
|
|
shift_right <= 1;
|
|
|
|
|
|
|
|
default: shift_right <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b0x1x_1010, // ROL A, ROR A
|
|
|
|
8'b0x1x_x110: // ROR, ROL
|
2019-04-22 14:09:33 +00:00
|
|
|
rotate <= 1;
|
|
|
|
|
|
|
|
default: rotate <= 0;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b00xx_xx10: // ROL, ASL
|
2019-04-22 14:09:33 +00:00
|
|
|
op <= OP_ROL;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b0010_x100: // BIT zp/abs
|
2019-04-22 14:09:33 +00:00
|
|
|
op <= OP_AND;
|
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b01xx_xx10: // ROR, LSR
|
2019-04-22 14:09:33 +00:00
|
|
|
op <= OP_A;
|
|
|
|
|
|
|
|
8'b1000_1000, // DEY
|
|
|
|
8'b1100_1010, // DEX
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b110x_x110, // DEC
|
|
|
|
8'b11xx_xx01, // CMP, SBC
|
|
|
|
8'b11x0_0x00, // CPX, CPY (imm, zpg)
|
|
|
|
8'b11x0_1100: op <= OP_SUB;
|
2019-04-22 14:09:33 +00:00
|
|
|
|
2019-08-29 13:36:18 +00:00
|
|
|
8'b010x_xx01, // EOR
|
|
|
|
8'b00xx_xx01: // ORA, AND
|
2019-04-22 14:09:33 +00:00
|
|
|
op <= { 2'b11, IR[6:5] };
|
|
|
|
|
|
|
|
default: op <= OP_ADD;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY )
|
2019-08-29 13:36:18 +00:00
|
|
|
casex( IR )
|
|
|
|
8'b0010_x100: // BIT zp/abs
|
2019-04-22 14:09:33 +00:00
|
|
|
bit_ins <= 1;
|
|
|
|
|
|
|
|
default: bit_ins <= 0;
|
|
|
|
endcase
|
|
|
|
/*
|
|
|
|
* special instructions
|
|
|
|
*/
|
|
|
|
always @(posedge clk )
|
|
|
|
if( state == DECODE && RDY ) begin
|
|
|
|
php <= (IR == 8'h08);
|
|
|
|
clc <= (IR == 8'h18);
|
|
|
|
plp <= (IR == 8'h28);
|
|
|
|
sec <= (IR == 8'h38);
|
|
|
|
cli <= (IR == 8'h58);
|
|
|
|
sei <= (IR == 8'h78);
|
|
|
|
clv <= (IR == 8'hb8);
|
|
|
|
cld <= (IR == 8'hd8);
|
|
|
|
sed <= (IR == 8'hf8);
|
|
|
|
brk <= (IR == 8'h00);
|
|
|
|
end
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
if( RDY )
|
|
|
|
cond_code <= IR[7:5];
|
|
|
|
|
|
|
|
always @*
|
|
|
|
case( cond_code )
|
|
|
|
3'b000: cond_true = ~N;
|
|
|
|
3'b001: cond_true = N;
|
|
|
|
3'b010: cond_true = ~V;
|
|
|
|
3'b011: cond_true = V;
|
|
|
|
3'b100: cond_true = ~C;
|
|
|
|
3'b101: cond_true = C;
|
|
|
|
3'b110: cond_true = ~Z;
|
|
|
|
3'b111: cond_true = Z;
|
|
|
|
endcase
|
|
|
|
|
|
|
|
|
|
|
|
reg NMI_1 = 0; // delayed NMI signal
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
NMI_1 <= NMI;
|
|
|
|
|
|
|
|
always @(posedge clk )
|
|
|
|
if( NMI_edge && state == BRK3 )
|
|
|
|
NMI_edge <= 0;
|
|
|
|
else if( NMI & ~NMI_1 )
|
|
|
|
NMI_edge <= 1;
|
|
|
|
|
|
|
|
endmodule
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALU.
|
|
|
|
*
|
|
|
|
* AI and BI are 8 bit inputs. Result in OUT.
|
|
|
|
* CI is Carry In.
|
|
|
|
* CO is Carry Out.
|
|
|
|
*
|
|
|
|
* op[3:0] is defined as follows:
|
|
|
|
*
|
|
|
|
* 0011 AI + BI
|
|
|
|
* 0111 AI - BI
|
|
|
|
* 1011 AI + AI
|
|
|
|
* 1100 AI | BI
|
|
|
|
* 1101 AI & BI
|
|
|
|
* 1110 AI ^ BI
|
|
|
|
* 1111 AI
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
module ALU( clk, op, right, AI, BI, CI, CO, BCD, OUT, V, Z, N, HC, RDY );
|
|
|
|
input clk;
|
|
|
|
input right;
|
|
|
|
input [3:0] op; // operation
|
|
|
|
input [7:0] AI;
|
|
|
|
input [7:0] BI;
|
|
|
|
input CI;
|
|
|
|
input BCD; // BCD style carry
|
|
|
|
output [7:0] OUT;
|
|
|
|
output CO;
|
|
|
|
output V;
|
|
|
|
output Z;
|
|
|
|
output N;
|
|
|
|
output HC;
|
|
|
|
input RDY;
|
|
|
|
|
|
|
|
reg [7:0] OUT;
|
|
|
|
reg CO;
|
|
|
|
wire V;
|
|
|
|
wire Z;
|
|
|
|
reg N;
|
|
|
|
reg HC;
|
|
|
|
|
|
|
|
reg AI7;
|
|
|
|
reg BI7;
|
|
|
|
reg [8:0] temp_logic;
|
|
|
|
reg [7:0] temp_BI;
|
|
|
|
reg [4:0] temp_l;
|
|
|
|
reg [4:0] temp_h;
|
|
|
|
wire [8:0] temp = { temp_h, temp_l[3:0] };
|
|
|
|
wire adder_CI = (right | (op[3:2] == 2'b11)) ? 0 : CI;
|
|
|
|
|
|
|
|
// calculate the logic operations. The 'case' can be done in 1 LUT per
|
|
|
|
// bit. The 'right' shift is a simple mux that can be implemented by
|
|
|
|
// F5MUX.
|
|
|
|
always @* begin
|
|
|
|
case( op[1:0] )
|
|
|
|
2'b00: temp_logic = {1'b0, AI | BI};
|
|
|
|
2'b01: temp_logic = {1'b0, AI & BI};
|
|
|
|
2'b10: temp_logic = {1'b0, AI ^ BI};
|
|
|
|
2'b11: temp_logic = {1'b0, AI};
|
|
|
|
endcase
|
|
|
|
|
|
|
|
if( right )
|
|
|
|
temp_logic = { AI[0], CI, AI[7:1] };
|
|
|
|
end
|
|
|
|
|
|
|
|
// Add logic result to BI input. This only makes sense when logic = AI.
|
|
|
|
// This stage can be done in 1 LUT per bit, using carry chain logic.
|
|
|
|
always @* begin
|
|
|
|
case( op[3:2] )
|
|
|
|
2'b00: temp_BI = BI; // A+B
|
|
|
|
2'b01: temp_BI = ~BI; // A-B
|
|
|
|
2'b10: temp_BI = temp_logic[7:0]; // A+A
|
|
|
|
2'b11: temp_BI = 0; // A+0
|
|
|
|
endcase
|
|
|
|
end
|
|
|
|
|
|
|
|
// HC9 is the half carry bit when doing BCD add
|
|
|
|
wire HC9 = BCD & (temp_l[3:1] >= 3'd5);
|
|
|
|
|
|
|
|
// CO9 is the carry-out bit when doing BCD add
|
|
|
|
wire CO9 = BCD & (temp_h[3:1] >= 3'd5);
|
|
|
|
|
|
|
|
// combined half carry bit
|
|
|
|
wire temp_HC = temp_l[4] | HC9;
|
|
|
|
|
|
|
|
// perform the addition as 2 separate nibble, so we get
|
|
|
|
// access to the half carry flag
|
|
|
|
always @* begin
|
2021-07-07 16:06:22 +00:00
|
|
|
temp_l = temp_logic[3:0] + temp_BI[3:0] + {4'b0,adder_CI};
|
|
|
|
temp_h = temp_logic[8:4] + temp_BI[7:4] + {4'b0,temp_HC};
|
2019-04-22 14:09:33 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
// calculate the flags
|
|
|
|
always @(posedge clk)
|
|
|
|
if( RDY ) begin
|
|
|
|
AI7 <= AI[7];
|
|
|
|
BI7 <= temp_BI[7];
|
|
|
|
OUT <= temp[7:0];
|
|
|
|
CO <= temp[8] | CO9;
|
|
|
|
N <= temp[7];
|
|
|
|
HC <= temp_HC;
|
|
|
|
end
|
|
|
|
|
|
|
|
assign V = AI7 ^ BI7 ^ CO ^ N;
|
|
|
|
assign Z = ~|OUT;
|
|
|
|
|
|
|
|
endmodule
|
|
|
|
|
|
|
|
// test module
|
|
|
|
module cpu6502_test_top(clk, reset, AB, DI, DO, WE);
|
|
|
|
input clk,reset;
|
|
|
|
output reg [15:0] AB; // address bus
|
2021-07-07 16:06:22 +00:00
|
|
|
output reg [7:0] DI; // data in, read bus
|
2019-04-22 14:09:33 +00:00
|
|
|
output wire [7:0] DO; // data out, write bus
|
|
|
|
output wire WE; // write enable
|
|
|
|
wire IRQ=0; // interrupt request
|
|
|
|
wire NMI=0; // non-maskable interrupt request
|
|
|
|
wire RDY=1; // Ready signal. Pauses CPU when RDY=0
|
|
|
|
|
|
|
|
cpu6502 cpu( clk, reset, AB, DI, DO, WE, IRQ, NMI, RDY );
|
|
|
|
|
|
|
|
always @(posedge clk)
|
|
|
|
begin
|
|
|
|
DI <= rom[AB[3:0]];
|
|
|
|
end
|
|
|
|
|
|
|
|
reg [7:0] rom[0:15];
|
2021-06-03 23:17:06 +00:00
|
|
|
// LDY #$13
|
|
|
|
// .loop: DEY
|
|
|
|
// BNE .loop
|
|
|
|
// BRK
|
2019-04-22 14:09:33 +00:00
|
|
|
initial begin
|
2021-07-07 16:06:22 +00:00
|
|
|
rom[0] = 8'ha0;
|
|
|
|
rom[1] = 8'h13;
|
|
|
|
rom[2] = 8'h88;
|
|
|
|
rom[3] = 8'hd0;
|
|
|
|
rom[4] = 8'hfd;
|
|
|
|
rom[5] = 8'h00;
|
2019-04-22 14:09:33 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
endmodule
|