/* verilator lint_off CASEX */ /* verilator lint_off CASEINCOMPLETE */ /* verilator lint_off CASEOVERLAP */ /* verilator lint_off SYNCASYNCNET */ /* * verilog model of 6502 CPU. * * (C) Arlet Ottens, * * https://github.com/Arlet/verilog-6502/ * * 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. */ 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 ) PC <= PC_temp + 16'(PC_inc); /* * 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 casex( {adj_bcd, adc_bcd, HC} ) 3'b0xx: ADJL = 4'd0; // no BCD instruction 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 casex( {adj_bcd, adc_bcd, CO} ) 3'b0xx: ADJH = 4'd0; // no BCD instruction 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; /* * Microcode state machine */ always @(posedge clk or posedge reset) if( reset ) state <= BRK0; else if( RDY ) case( state ) DECODE : casex ( IR ) 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; 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; // A, TXA, ... NOP 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 ) 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 8'b10111010, // TSX 8'b1011x1x0, // LDX/LDY 8'b11001010, // DEX 8'b1x1xxx01, // LDA, SBC 8'bxxx01000: // DEY, TAY, INY, INX load_reg <= 1; default: load_reg <= 0; endcase always @(posedge clk) if( state == DECODE && RDY ) casex( IR ) 8'b1110_1000, // INX 8'b1100_1010, // DEX 8'b101x_xx10: // LDX, TAX, TSX dst_reg <= SEL_X; 8'b0x00_1000, // PHP, PHA 8'b1001_1010: // TXS dst_reg <= SEL_S; 8'b1x00_1000, // DEY, DEX 8'b101x_x100, // LDY 8'b1010_x000: // LDY #imm, TAY dst_reg <= SEL_Y; default: dst_reg <= SEL_A; endcase always @(posedge clk) if( state == DECODE && RDY ) casex( IR ) 8'b1011_1010: // TSX src_reg <= SEL_S; 8'b100x_x110, // STX 8'b100x_1x10, // TXA, TXS 8'b1110_xx00, // INX, CPX 8'b1100_1010: // DEX src_reg <= SEL_X; 8'b100x_x100, // STY 8'b1001_1000, // TYA 8'b1100_xx00, // CPY 8'b1x00_1000: // DEY, INY src_reg <= SEL_Y; default: src_reg <= SEL_A; endcase always @(posedge clk) if( state == DECODE && RDY ) casex( IR ) 8'bxxx1_0001, // INDY 8'b10x1_x110, // LDX/STX zpg/abs, Y 8'bxxxx_1001: // abs, Y index_y <= 1; default: index_y <= 0; endcase always @(posedge clk) if( state == DECODE && RDY ) casex( IR ) 8'b100x_x1x0, // STX, STY 8'b100x_xx01: // STA store <= 1; default: store <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b0xxx_x110, // ASL, ROL, LSR, ROR 8'b11xx_x110: // DEC/INC write_back <= 1; default: write_back <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b101x_xxxx: // LDA, LDX, LDY load_only <= 1; default: load_only <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b111x_x110, // INC 8'b11x0_1000: // INX, INY inc <= 1; default: inc <= 0; endcase always @(posedge clk ) if( (state == DECODE || state == BRK0) && RDY ) casex( IR ) 8'bx11x_xx01: // SBC, ADC adc_sbc <= 1; default: adc_sbc <= 0; endcase always @(posedge clk ) if( (state == DECODE || state == BRK0) && RDY ) casex( IR ) 8'b011x_xx01: // ADC adc_bcd <= D; default: adc_bcd <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b0xxx_x110, // ASL, ROL, LSR, ROR (abs, absx, zpg, zpgx) 8'b0xxx_1010: // ASL, ROL, LSR, ROR (acc) shift <= 1; default: shift <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b11x0_0x00, // CPX, CPY (imm/zp) 8'b11x0_1100, // CPX, CPY (abs) 8'b110x_xx01: // CMP compare <= 1; default: compare <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b01xx_xx10: // ROR, LSR shift_right <= 1; default: shift_right <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b0x1x_1010, // ROL A, ROR A 8'b0x1x_x110: // ROR, ROL rotate <= 1; default: rotate <= 0; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b00xx_xx10: // ROL, ASL op <= OP_ROL; 8'b0010_x100: // BIT zp/abs op <= OP_AND; 8'b01xx_xx10: // ROR, LSR op <= OP_A; 8'b1000_1000, // DEY 8'b1100_1010, // DEX 8'b110x_x110, // DEC 8'b11xx_xx01, // CMP, SBC 8'b11x0_0x00, // CPX, CPY (imm, zpg) 8'b11x0_1100: op <= OP_SUB; 8'b010x_xx01, // EOR 8'b00xx_xx01: // ORA, AND op <= { 2'b11, IR[6:5] }; default: op <= OP_ADD; endcase always @(posedge clk ) if( state == DECODE && RDY ) casex( IR ) 8'b0010_x100: // BIT zp/abs 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 temp_l = temp_logic[3:0] + temp_BI[3:0] + 5'(adder_CI); temp_h = temp_logic[8:4] + temp_BI[7:4] + 5'(temp_HC); 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 output wire [7:0] DI; // data in, read bus 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]; initial begin rom = '{ 8'ha0,8'h13, 8'h88, 8'hd0,8'hfd, 0,0,0, 0,0, 0,0, 0,0, 0,0 }; end endmodule