Projects/MCL65/MCL65.v

478 lines
15 KiB
Verilog

//
//
// File Name : MCL65.v
// Used on :
// Author : Ted Fried, MicroCore Labs
// Creation : 8/12/2017
// Code Type : Synthesizable
//
// Description:
// ============
//
// Microsequencer implementation of the MOS 6502 microprocessor
//
//------------------------------------------------------------------------
//
// Modification History:
// =====================
//
// Revision 1 8/12/17
// Initial revision
//
//
//------------------------------------------------------------------------
//
// Copyright (c) 2020 Ted Fried
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
//------------------------------------------------------------------------
module MCL65
(
input CORE_CLK, // Microsequencer Core Clock
input CLK0, // 6502 Bus Signals
output CLK1,
output CLK2,
input RESET_n,
input NMI_n,
input IRQ_n,
input SO,
output SYNC,
output RDWR_n,
input READY,
output [15:0] A,
inout [7:0] D,
output DIR0,
output DIR1
);
//------------------------------------------------------------------------
// Internal Signals
reg add_carry8 = 'h0;
reg add_overflow8 = 'h0;
reg clk1_out_int = 'h0;
reg clk2_out_int = 'h0;
reg clk0_int_d1 = 'h0;
reg clk0_int_d2 = 'h0;
reg clk0_int_d3 = 'h0;
reg clk0_int_d4 = 'h0;
reg reset_n_d1 = 'h0;
reg reset_n_d2 = 'h0;
reg nmi_n_d1 = 'h0;
reg nmi_n_d2 = 'h0;
reg nmi_n_d3 = 'h0;
reg nmi_asserted = 'h0;
reg irq_d1 = 'h0;
reg irq_d2 = 'h0;
reg irq_d3 = 'h0;
reg irq_d4 = 'h0;
reg irq_gated = 'h0;
reg so_n_d1 = 'h0;
reg so_n_d2 = 'h0;
reg so_n_d3 = 'h0;
reg so_asserted = 'h0;
reg stall_pipeline = 'h0;
reg sync_int_d1 = 'h0;
reg rdwr_n_int_d1 = 'h0;
reg rdwr_n_int_d2 = 'h0;
reg ready_int_d1 = 'h0;
reg ready_int_d2 = 'h0;
reg ready_int_d3 = 'h0;
reg dataout_enable = 'h0;
wire flag_n;
wire flag_v;
wire flag_b;
wire flag_d;
wire flag_i;
wire flag_z;
wire flag_c;
wire nmi_debounce;
wire so_debounce;
wire opcode_jump_call;
wire jump_boolean;
wire sync_int;
wire rdwr_n_int;
reg [10:0] rom_address = 'h0;
reg [21:0] calling_address = 'h0;
reg [7:0] register_a = 8'h0;
reg [7:0] register_x = 8'h0;
reg [7:0] register_y = 8'h0;
reg [15:0] register_pc = 'h0;
reg [7:0] register_sp = 8'h0;
reg [15:0] register_r0 = 'h0;
reg [15:0] register_r1 = 'h0;
reg [15:0] register_r2 = 'h0;
reg [15:0] register_r3 = 'h0;
reg [15:0] alu_last_result = 'h0;
reg [15:0] address_out = 'h0;
reg [4:0] system_output = 5'h01;
reg [7:0] data_out = 'h0;
reg [7:0] data_in_d1 = 'h0;
reg [7:0] data_in_d2 = 'h0;
reg [7:0] register_flags = 8'h00;
reg [15:0] a_out_int = 'h0;
reg [7:0] d_out_int = 'h0;
wire [15:0] adder_out;
wire [16:0] carry;
wire [2:0] opcode_type;
wire [3:0] opcode_dst_sel;
wire [3:0] opcode_op0_sel;
wire [3:0] opcode_op1_sel;
wire [15:0] opcode_immediate;
wire [2:0] opcode_jump_src;
wire [3:0] opcode_jump_cond;
wire [15:0] system_status;
wire [15:0] alu2;
wire [15:0] alu3;
wire [15:0] alu4;
wire [15:0] alu5;
wire [15:0] alu6;
wire [15:0] alu_out;
wire [15:0] operand0;
wire [15:0] operand1;
wire [31:0] rom_data;
//------------------------------------------------------------------------
//
// 2Kx32 Microcode ROM
//
//------------------------------------------------------------------------
ROM_2Kx32 microcode_rom
(
.clka (CORE_CLK),
.addra (rom_address[10:0]),
.douta (rom_data)
);
//------------------------------------------------------------------------
//
// Combinationals
//
//------------------------------------------------------------------------
assign A = a_out_int;
assign D = (dataout_enable==1'b1) ? d_out_int : 8'hZZ;
assign DIR0 = dataout_enable;
assign DIR1 = dataout_enable;
assign CLK1 = clk1_out_int;
assign CLK2 = clk2_out_int;
assign so_debounce = system_output[4];
assign nmi_debounce = system_output[3];
//assign dataout_enable = system_output[2];
assign sync_int = system_output[1];
assign rdwr_n_int = system_output[0];
assign SYNC = sync_int_d1;
assign RDWR_n = rdwr_n_int_d1;
// Microcode ROM opcode decoder
assign opcode_type = rom_data[30:28];
assign opcode_dst_sel = rom_data[27:24];
assign opcode_op0_sel = rom_data[23:20];
assign opcode_op1_sel = rom_data[19:16];
assign opcode_immediate = rom_data[15:0];
assign opcode_jump_call = rom_data[24];
assign opcode_jump_src = rom_data[22:20];
assign opcode_jump_cond = rom_data[19:16];
assign operand0 = (opcode_op0_sel==4'h0) ? register_r0 :
(opcode_op0_sel==4'h1) ? register_r1 :
(opcode_op0_sel==4'h2) ? register_r2 :
(opcode_op0_sel==4'h3) ? register_r3 :
(opcode_op0_sel==4'h4) ? { 8'h00 , register_a } :
(opcode_op0_sel==4'h5) ? { 8'h00 , register_x } :
(opcode_op0_sel==4'h6) ? { 8'h00 , register_y } :
(opcode_op0_sel==4'h7) ? register_pc :
(opcode_op0_sel==4'h8) ? { 8'h01 , register_sp } :
(opcode_op0_sel==4'h9) ? { 8'h00 , register_flags } :
(opcode_op0_sel==4'hA) ? address_out :
(opcode_op0_sel==4'hB) ? { data_in_d2 , data_in_d2 } :
(opcode_op0_sel==4'hC) ? system_status :
(opcode_op0_sel==4'hD) ? { 11'h000 , system_output[4:0] } :
//(opcode_op0_sel==4'hE) ? xxxx :
16'h0 ;
assign operand1 = (opcode_op1_sel==4'h0) ? register_r0 :
(opcode_op1_sel==4'h1) ? register_r1 :
(opcode_op1_sel==4'h2) ? register_r2 :
(opcode_op1_sel==4'h3) ? register_r3 :
(opcode_op1_sel==4'h4) ? { 8'h00 , register_a } :
(opcode_op1_sel==4'h5) ? { 8'h00 , register_x } :
(opcode_op1_sel==4'h6) ? { 8'h00 , register_y } :
(opcode_op1_sel==4'h7) ? { register_pc[7:0] , register_pc[15:8] } :
(opcode_op1_sel==4'h8) ? { 8'h01 , register_sp } :
(opcode_op1_sel==4'h9) ? { 8'h00 , register_flags } :
(opcode_op1_sel==4'hA) ? address_out :
(opcode_op1_sel==4'hB) ? { data_in_d2 , data_in_d2 } :
(opcode_op1_sel==4'hC) ? system_status :
(opcode_op1_sel==4'hD) ? { 11'h000 , system_output[4:0] } :
//(opcode_op1_sel==4'hE) ? xxxx :
opcode_immediate ;
// JUMP condition codes
assign jump_boolean = (opcode_jump_cond==4'h0) ? 1'b1 : // Unconditional jump
(opcode_jump_cond==4'h1 && alu_last_result!=16'h0) ? 1'b1 : // Jump Not Zero
(opcode_jump_cond==4'h2 && alu_last_result==16'h0) ? 1'b1 : // Jump Zero
(opcode_jump_cond==4'h3 && clk0_int_d2==1'b0) ? 1'b1 : // Jump backwards until CLK=1
(opcode_jump_cond==4'h4 && rdwr_n_int_d1==1'b0 && clk0_int_d2==1'b1) ? 1'b1 : // Jump backwards until CLK=0 for write cycles. READY ignored
(opcode_jump_cond==4'h4 && rdwr_n_int_d1==1'b1 && (clk0_int_d2==1'b1 || ready_int_d3==1'b0)) ? 1'b1 : // Jump backwards until CLK=0 for read cycles with READY active
1'b0 ;
// System status
assign system_status[15:7] = 'h0;
assign system_status[6] = add_overflow8;
assign system_status[5] = irq_gated;
assign system_status[4] = so_asserted;
assign system_status[3] = nmi_asserted;
assign system_status[2] = 1'b0;
assign system_status[1] = 1'b0;
assign system_status[0] = add_carry8;
assign flag_n = register_flags[7];
assign flag_v = register_flags[6];
assign flag_b = register_flags[4];
assign flag_d = register_flags[3];
assign flag_i = register_flags[2];
assign flag_z = register_flags[1];
assign flag_c = register_flags[0];
// Microsequencer ALU Operations
// ------------------------------------------
// alu0 = NOP
// alu1 = JUMP
assign alu2 = adder_out; // ADD
assign alu3 = operand0 & operand1; // AND
assign alu4 = operand0 | operand1; // OR
assign alu5 = operand0 ^ operand1; // XOR
assign alu6 = { 1'b0 , operand0[15:1] }; // SHR
// Mux the ALU operations
assign alu_out = (opcode_type==3'h2) ? alu2 :
(opcode_type==3'h3) ? alu3 :
(opcode_type==3'h4) ? alu4 :
(opcode_type==3'h5) ? alu5 :
(opcode_type==3'h6) ? alu6 :
16'hEEEE;
// Generate 16-bit full adder
assign carry[0] = 1'b0;
genvar i;
generate
for (i=0; i < 16; i=i+1)
begin : GEN_ADDER
assign adder_out[i] = operand0[i] ^ operand1[i] ^ carry[i];
assign carry[i+1] = (operand0[i] & operand1[i]) | (operand0[i] & carry[i]) | (operand1[i] & carry[i]);
end
endgenerate
//------------------------------------------------------------------------------------------
//
// Microsequencer
//
//------------------------------------------------------------------------------------------
always @(posedge CORE_CLK)
begin : MICROSEQUENCER
clk0_int_d1 <= CLK0;
clk0_int_d2 <= clk0_int_d1;
clk0_int_d3 <= clk0_int_d2;
clk0_int_d4 <= clk0_int_d3;
clk1_out_int <= ~clk0_int_d3;
clk2_out_int <= clk0_int_d2;
reset_n_d1 <= RESET_n;
reset_n_d2 <= reset_n_d1;
ready_int_d1 <= READY;
ready_int_d2 <= ready_int_d1;
ready_int_d3 <= ready_int_d2;
sync_int_d1 <= sync_int;
rdwr_n_int_d1 <= rdwr_n_int;
rdwr_n_int_d2 <= rdwr_n_int_d1;
a_out_int <= address_out;
d_out_int <= data_out;
irq_d1 <= ~IRQ_n;
data_in_d1 <= D;
if (clk0_int_d3==1'b1 && clk0_int_d2==1'b0) // Store data and sample IRQ_n on falling edge of clk
begin
data_in_d2 <= data_in_d1;
irq_d2 <= irq_d1;
irq_d3 <= irq_d2;
irq_d4 <= irq_d3;
end
irq_gated <= irq_d4 & ~flag_i;
if (rdwr_n_int_d1==1'b0 && clk0_int_d4==1'b1)
begin
dataout_enable <= 1'b1;
end
else if (rdwr_n_int_d2==1'b0 && rdwr_n_int_d1==1'b1)
begin
dataout_enable <= 1'b0;
end
nmi_n_d1 <= NMI_n;
nmi_n_d2 <= nmi_n_d1;
nmi_n_d3 <= nmi_n_d2;
if (nmi_debounce==1'b1)
begin
nmi_asserted <= 1'b0;
end
else if (nmi_n_d3==1'b1 && nmi_n_d2==1'b0) // Falling edge of NMI_n
begin
nmi_asserted <= 1'b1;
end
so_n_d1 <= SO;
so_n_d2 <=so_n_d1;
so_n_d3 <=so_n_d2;
if (so_debounce==1'b1)
begin
so_asserted <= 1'b0;
end
else if (so_n_d3==1'b1 && so_n_d2==1'b0) // Falling edge of SO
begin
so_asserted <= 1'b1;
end
// Generate and store flags for addition
if (stall_pipeline==1'b0 && opcode_type==3'h2)
begin
add_carry8 <= carry[8];
add_overflow8 <= carry[8] ^ carry[7];
end
// Register writeback
if (stall_pipeline==1'b0 && opcode_type!=3'h0 && opcode_type!=3'h1)
begin
alu_last_result <= alu_out[15:0];
case (opcode_dst_sel) // synthesis parallel_case
4'h0 : register_r0 <= alu_out[15:0];
4'h1 : register_r1 <= alu_out[15:0];
4'h2 : register_r2 <= alu_out[15:0];
4'h3 : register_r3 <= alu_out[15:0];
4'h4 : register_a <= alu_out[7:0];
4'h5 : register_x <= alu_out[7:0];
4'h6 : register_y <= alu_out[7:0];
4'h7 : register_pc <= alu_out[15:0];
4'h8 : register_sp <= alu_out[7:0];
4'h9 : register_flags <= { alu_out[7:6] , 2'b11 , alu_out[3:0] };
4'hA : address_out <= alu_out[15:0];
4'hB : data_out <= alu_out[7:0];
//4'hC :
4'hD : system_output <= alu_out[4:0];
//4'hE :
//4'hF :
default : ;
endcase
end
if (reset_n_d2==1'b0)
begin
rom_address <= 11'h7D0; // Microcode starts here after reset
stall_pipeline <= 'h0;
end
// JUMP Opcode
else if (stall_pipeline==1'b0 && opcode_type==3'h1 && jump_boolean==1'b1)
begin
stall_pipeline <= 1'b1;
// For subroutine CALLs, store next opcode address
if (opcode_jump_call==1'b1)
begin
calling_address[21:0] <= {calling_address[10:0] , rom_address[10:0] }; // Two deep stack for calling addresses
end
case (opcode_jump_src) // synthesis parallel_case
3'h0 : rom_address <= opcode_immediate[10:0];
3'h1 : rom_address <= { 3'b000 , data_in_d2[7:0] }; // Opcode Jump Table
3'h2 : begin
rom_address <= calling_address[10:0];
calling_address[10:0] <= calling_address[21:11];
end
3'h3 : rom_address <= rom_address - 1'b1;
default : ;
endcase
end
else
begin
stall_pipeline <= 1'b0; // Debounce the pipeline stall
rom_address <= rom_address + 1'b1;
end
end // MCL65 Microsequencer
endmodule // MCL65.v