Лисья Нора

§ Процессор

Проверенный с помощью тестов процессор для NES эмулятора.
Дата ревизии: 4 сен 2024
/* verilator lint_off WIDTHEXPAND */
/* verilator lint_off WIDTHTRUNC */
/* verilator lint_off CASEX */
/* verilator lint_off CASEOVERLAP */
/* verilator lint_off CASEINCOMPLETE */
 
module cpu
(
// --- Управление, тактовая частота ---
input clock, // 25 Mhz
input reset_n, // ResetN
input ce, // ChipEnabled
input nmi, // Из PPU
output m0, // Такт #0
// --- Регистры ---
output reg [ 7:0] a,
output reg [ 7:0] x,
output reg [ 7:0] y,
output reg [ 7:0] p,
output reg [ 7:0] s,
output reg [15:0] pc,
// --- Интерфейс работы с памятью ---
output [15:0] A, // Адрес
input [ 7:0] I, // Данные
output reg [ 7:0] D, // Выход
output reg R, // Чтение
output reg W // Запись
);
 
assign A = m ? cp : pc;
assign m0 = (t == LOAD);
 
// Объявления
// ---------------------------------------------------------------------
localparam
 
LOAD = 5'h00, NDX = 5'h01, NDY = 5'h02, ABX = 5'h03,
ABY = 5'h04, ABS = 5'h05, REL = 5'h06, RUN = 5'h07,
ZP = 5'h08, ZPX = 5'h09, ZPY = 5'h0A, NDX2 = 5'h0B,
NDX3 = 5'h0C, LAT = 5'h0D, NDY2 = 5'h0E, NDY3 = 5'h0F,
ABS2 = 5'h10, ABXY = 5'h11, REL1 = 5'h12, REL2 = 5'h13,
BRK = 5'h14, JSR = 5'h15, RTS = 5'h16, RTI = 5'h17;
 
// Номер АЛУ
localparam
 
ORA = 0, AND = 1, EOR = 2, ADC = 3, STA = 4, LDA = 5, CMP = 6, SBC = 7,
ASL = 8, ROL = 9, LSR = 10, ROR = 11, BIT = 12, DEC = 14, INC = 15;
 
// Позиции флагов
localparam CF = 0, ZF = 1, IF = 2, DF = 3, BF = 4, VF = 6, SF = 7;
localparam IRQ_NMI = 2'b01, IRQ_RST = 2'b10, IRQ_BRK = 2'b11;
localparam
 
DST_A = 2'b00, DST_X = 2'b01, DST_Y = 2'b10, DST_S = 2'b11,
SRC_D = 2'b00, SRC_X = 2'b01, SRC_Y = 2'b10, SRC_A = 2'b11;
 
// Состояние процессора
// ---------------------------------------------------------------------
reg m; // Выбор памяти =0 PC; =1 CP
reg rd; // =1 То читать из памяти =0 Писать
reg [ 4:0] t; // T-State
reg [ 2:0] n; // N-State
reg [15:0] cp; // Указатель памяти
reg [ 7:0] opcode; // Сохранение опкода
reg [ 7:0] tr; // Временный регистр
reg [ 1:0] intr; // =0 =1 =2 =3 USR
reg cout; // Перенос во время вычисления адресов
reg cnext; // =1 Разная задержка из-за инструкции
reg nmitr; // NMI сохранненый сигнал
 
// Вычисления
// ---------------------------------------------------------------------
wire [ 8:0] Xi = x + I;
wire [ 8:0] Yi = y + I;
wire [15:0] pcn = pc + 1;
wire [15:0] pcr = pcn + {{8{I[7]}}, I};
wire [15:0] cpn = cp + 1;
 
// Сложение 16-битного адреса с переносом
wire [15:0] itr = {I, tr};
wire [15:0] cpc = itr + {cout, 8'h00};
wire [ 3:0] branch = {p[ZF], p[CF], p[VF], p[SF]};
 
// Либо +1T, либо к RUN
wire [ 4:0] NEXT = (cout || cnext) ? LAT : RUN;
 
// АЛУ
// ---------------------------------------------------------------------
reg [ 3:0] alu;
reg [ 1:0] dst_r, src_r;
 
// Левый [dst] и правый [src] операнд
wire [ 7:0] dst = dst_r == DST_A ? a : dst_r == DST_X ? x : dst_r == DST_Y ? y : s;
wire [ 7:0] src = src_r == SRC_D ? I : src_r == SRC_X ? x : src_r == SRC_Y ? y : a;
 
// Результат исполнения */
wire [8:0] ar =
 
// Арифметика
alu == ORA ? dst | src :
alu == AND ? dst & src :
alu == EOR ? dst ^ src :
alu == ADC ? dst + src + cin :
alu == STA ? dst :
alu == LDA ? src :
alu == CMP ? dst - src :
alu == SBC ? dst - src - !cin :
// Сдвиги
alu == ASL ? {src[6:0], 1'b0} :
alu == ROL ? {src[6:0], cin} :
alu == LSR ? {1'b0, src[7:1]} :
alu == ROR ? {cin, src[7:1]} :
// Разное
alu == BIT ? dst & src :
alu == DEC ? src - 1 :
alu == INC ? src + 1 : src;
 
// Статусы ALU
wire zf = ar[7:0] == 0;
wire sf = ar[7];
wire oadc = (dst[7] ^ src[7] ^ 1'b1) & (dst[7] ^ ar[7]); // Переполнение ADC
wire osbc = (dst[7] ^ src[7] ) & (dst[7] ^ ar[7]); // Переполнение SBC
wire cin = p[CF];
wire carry = ar[8];
 
// Вычисление флагов
wire [7:0] ap =
 
alu[3:1] == 3'b000_ || // ORA, AND
alu[3:0] == 4'b0010 || // EOR
alu[3:1] == 3'b010_ || // STA, LDA
alu[3:1] == 4'b111_ ? {sf, p[6:2], zf, p[0]} : // INC, DEC
alu[3:0] == 4'b0011 ? {sf, oadc, p[5:2], zf, carry} : // ADC
alu[3:0] == 4'b0111 ? {sf, osbc, p[5:2], zf, ~carry} : // SBC
alu[3:0] == 4'b0110 ? {sf, p[6:2], zf, ~carry} : // CMP
alu[3:1] == 3'b100_ ? {sf, p[6:2], zf, src[7]} : // ASL, ROL
alu[3:1] == 3'b101_ ? {sf, p[6:2], zf, src[0]} : // LSR, ROR
alu[3:0] == 4'b1100 ? {src[7:6], p[5:2], zf, p[0]} : 8'hFF; // BIT
 
// Исполнение опкодов
// ---------------------------------------------------------------------
 
always @(posedge clock)
// Вызов процедуры BRK #1 RESET
if (reset_n == 1'b0) begin
 
t <= BRK;
m <= 0;
n <= 0;
a <= 8'h21;
x <= 8'h83;
y <= 8'h81;
s <= 8'h00;
// SV ZC
p <= 8'b0000_0010;
pc <= 16'h0000;
nmitr <= 1'b0;
intr <= IRQ_RST;
 
end
// Процессор работает только если CE=1
else if (ce) begin
 
R <= 0;
W <= 0;
 
case (t)
 
// Загрузка опкода
LOAD: begin
 
cout <= 0;
cnext <= 0; // =1 Некоторые инструкции удлиняют такт +1
rd <= 1; // =1 Используется для запроса чтения из PPU
n <= 0; // ID микрокода RUN
alu <= I[7:5]; // АЛУ по умолчанию
intr <= IRQ_BRK; // USER BRK
dst_r <= DST_A; // A
src_r <= SRC_D; // DataIn
 
// Прерывание NMI: срабатывает на восходящем сигнале
if (nmitr ^ nmi && nmi) begin
 
t <= BRK;
intr <= IRQ_NMI;
 
end else
// Считывание опкода
begin
 
pc <= pcn;
opcode <= I;
 
casex (I)
8'b001_000_00: begin t <= JSR; end
8'b010_000_00: begin t <= RTI; end
8'b011_000_00: begin t <= RTS; end
8'b000_000_00: begin t <= BRK; pc <= pc + 2; end
8'bxxx_000_x1: begin t <= NDX; end // Indirect,X
8'bxxx_010_x1,
8'b1xx_000_x0: begin t <= RUN; end // Immediate
8'bxxx_100_x1: begin t <= NDY; end // Indirect,Y
8'bxxx_110_x1: begin t <= ABY; end // Absolute,Y
8'bxxx_001_xx: begin t <= ZP; end // ZeroPage
8'bxxx_011_xx, // Absolute
8'b001_000_00: begin t <= ABS; end
8'b10x_101_1x: begin t <= ZPY; end // ZeroPage,Y
8'bxxx_101_xx: begin t <= ZPX; end // ZeroPage,X
8'b10x_111_1x: begin t <= ABY; end // Absolute,Y
8'bxxx_111_xx: begin t <= ABX; end // Absolute,X
8'bxxx_100_00: begin t <= REL; end // Relative
8'b0xx_010_10: begin t <= RUN; end // Accumulator
default: begin t <= RUN; end
endcase
 
// АЛУ
casex (I)
// CPY
8'hC0,
8'hC4,
8'hCC: begin alu <= CMP; dst_r <= DST_Y; end
// CPX
8'hE0,
8'hE4,
8'hEC: begin alu <= CMP; dst_r <= DST_X; end
// TXA, TYA
8'h8A: begin alu <= LDA; src_r <= SRC_X; end
8'h98: begin alu <= LDA; src_r <= SRC_Y; end
// TAX, TAY
8'hAA,
8'hA8: begin alu <= LDA; src_r <= SRC_A; end
// BIT
8'h24,
8'h2C: begin alu <= BIT; end
// DEX, INX, DEY, INY
8'hCA: begin alu <= DEC; src_r <= SRC_X; end
8'hE8: begin alu <= INC; src_r <= SRC_X; end
8'h88: begin alu <= DEC; src_r <= SRC_Y; end
8'hC8: begin alu <= INC; src_r <= SRC_Y; end
// AAC, ASR, ARR
8'h0B, 8'h2B, 8'h4B, 8'h6B: begin alu <= AND; end
// Сдвиги
8'b0xx_xx1_10: begin alu <= ASL + I[6:5]; end
8'b0xx_010_10: begin alu <= ASL + I[6:5]; src_r <= SRC_A; end
// INC, DEC
8'b11x_xx1_10: begin alu <= DEC + I[5]; end
endcase
 
// STX, STY, STA: Выбор источника для записи в память
casex (I) 8'b100_xx1_10: D <= x; 8'b100_xx1_00: D <= y; default: D <= a; endcase
 
// Для STA запретить RD, но разрешить WR
casex (I) 8'b100_xxx_01, 8'b100_xx1_x0: rd <= 1'b0; endcase
 
// INC, DEC, STA, STX, STY, ASL, LSR, ROL, ROR добавляют +1Т к ABS,XY; IND,Y
casex (I) 8'b100xxxxx, 8'b11xxx110, 8'b0xxxx110: cnext <= 1; endcase
 
end
 
nmitr <= nmi;
 
end
 
// Вычисление эффективного адреса
// -------------------------------------------------------------
 
// Indirect, X: Читать из #D+X значение 16-битного адреса
NDX: begin t <= NDX2; cp <= Xi[7:0]; m <= 1; pc <= pcn; end
NDX2: begin t <= NDX3; cp <= cpn[7:0]; tr <= I; end
NDX3: begin t <= LAT; cp <= itr; {R,W} <= {rd,~rd}; end
 
// Indirect, Y: Читать из (#D) 16 битный адрес + Y
NDY: begin t <= NDY2; cp <= I; m <= 1; pc <= pcn; end
NDY2: begin t <= NDY3; cp <= cpn[7:0]; {cout,tr} <= Yi; end
NDY3: begin t <= NEXT; cp <= cpc; {R,W} <= {rd,~rd}; end
 
// ZP; ZPX; ZPY: Адресация ZeroPage
ZP: begin t <= RUN; cp <= I; m <= 1; {R,W} <= {rd,~rd}; pc <= pcn; end
ZPX: begin t <= LAT; cp <= Xi[7:0]; m <= 1; {R,W} <= {rd,~rd}; pc <= pcn; end
ZPY: begin t <= LAT; cp <= Yi[7:0]; m <= 1; {R,W} <= {rd,~rd}; pc <= pcn; end
 
// Absolute: 16-битный адрес
ABS: begin t <= ABS2; tr <= I; pc <= pcn; end
ABS2: begin
 
if (opcode == 8'h4C) // 3T JMP ABS
begin t <= LOAD; pc <= itr; end
else begin t <= RUN; cp <= itr; pc <= pcn; m <= 1; {R,W} <= {rd,~rd}; end
 
end
 
// Absolute,X: 16-битный адрес + X|Y
ABX: begin t <= ABXY; tr <= Xi[7:0]; pc <= pcn; cout <= Xi[8]; end
ABY: begin t <= ABXY; tr <= Yi[7:0]; pc <= pcn; cout <= Yi[8]; end
ABXY: begin t <= NEXT; cp <= cpc; pc <= pcn; m <= 1; {R,W} <= {rd,~rd}; end
 
// Условный переход
// -------------------------------------------------------------
REL: begin
 
if (branch[opcode[7:6]] == opcode[5]) begin
 
t <= pcr[15:8] == pc[15:8] ? REL2 : REL1;
pc <= pcr;
 
end
else begin pc <= pcn; t <= LOAD; end
 
end
 
REL1: begin t <= REL2; end // +2T если превышение границ
REL2: begin t <= LOAD; end // +1T если переход
LAT: begin t <= RUN; end // +1T к такту
// -------------------------------------------------------------
 
RUN: begin
 
m <= 0;
t <= LOAD;
 
// Исполнение опкода
casex (opcode) 8'bxxx_010_x1, 8'b1xx_000_x0: pc <= pcn; endcase
casex (opcode)
 
// TXS, TSX
8'h9A: begin s <= x; end
8'hBA: begin x <= s; p[ZF] <= (s == 0); p[SF] <= s[7]; end
 
// LDX, LDY, TAX, TAY, DEX, DEY, INX, INY
8'b101_xx1_10, 8'hA2, 8'hAA, 8'hCA, 8'hE8: begin x <= ar[7:0]; p <= ap; end
8'b101_xx1_00, 8'hA0, 8'hA8, 8'h88, 8'hC8: begin y <= ar[7:0]; p <= ap; end
 
// CP[XY] D :: BIT
8'hC0,8'hC4,8'hCC,
8'hE0,8'hE4,8'hEC,
8'h24,8'h2C: begin p <= ap; end
 
// Инструкция [6T] STA,STX,STY
8'b100_xxx_01, 8'b100_xx1_x0: begin end
 
// CLC, SEC; CLI, SEI; CLV; CLD, SED
8'b00x_110_00: p[CF] <= opcode[5];
8'b01x_110_00: p[IF] <= opcode[5];
8'b101_110_00: p[VF] <= 1'b0;
8'b11x_110_00: p[DF] <= opcode[5];
 
// АЛУ [dst,D]; Сдвиги ACC; TRANSFER
8'bxxx_xxx_01: begin p <= ap; if (alu != CMP) a <= ar[7:0]; end
 
// СДВИГ IMM, TXA, TYA, SBC
8'b0xx_010_10,
8'h8A, 8'h98, 8'hEB: begin a <= ar[7:0]; p <= ap; end
 
// Недокументированные инструкции
// -----------------------
// AAC: AND + Carry
8'h0B,
8'h2B: begin a <= ar[7:0]; p <= ap; p[CF] <= ar[7]; end
 
// ASR: AND + LSR
8'h4B: begin a <= ar[7:1]; {p[CF], p[SF], p[ZF]} <= {ar[0], 1'b0, ar[7:1] == 0}; end
 
// ARR: AND + ROR
8'h6B: begin a <= {p[CF], ar[7:1]}; {p[VF], p[CF], p[ZF]} <= {ar[6] ^ ar[7], ar[7], {p[CF], ar[7:1]} == 0}; end
// -----------------------
 
// ASL, LSR, ROR, ROL, INC, DEC: Запись в память
8'b0xx_xx1_10,
8'b11x_xx1_10: case (n)
 
// Пишется сначала НЕ модифицированное значение [это важно для MMC1]
0: begin n <= 1; t <= RUN; W <= 1; D <= I; m <= 1; end
1: begin n <= 2; t <= RUN; W <= 1; D <= ar; m <= 1; p <= ap; end
 
endcase
 
// JMP [INDIRECT]
8'h6C: case (n)
 
0: begin n <= 1; m <= 1; t <= RUN; tr <= I; cp[7:0] <= cp[7:0] + 1; R <= 1; end
1: begin pc <= {I, tr}; end
 
endcase
 
// PHP, PHA
8'h08, 8'h48: if (n == 0) begin
 
t <= RUN;
m <= 1;
n <= 1;
cp <= {8'h01, s};
D <= opcode[6] ? a : (p | 8'h30);
W <= 1;
s <= s - 1;
 
end
 
// PLA, PLP
8'h68, 8'h28: case (n)
 
0: begin
 
t <= RUN;
n <= 1;
m <= 1;
s <= s + 1;
 
cp[15:8] <= 8'h01;
cp[ 7:0] <= s + 1;
 
end
 
1: begin
 
n <= 2;
t <= RUN;
 
if (opcode[6]) begin a <= I; p[ZF] <= I == 0; p[SF] <= I[7]; end
else begin p <= I; end
 
end
 
endcase
 
endcase
 
end
 
// -------------------------------------------------------------
 
// [7T] BRK или IRQ
BRK: case (n)
 
// PUSH((PC >> 8) & 0xff);
// PUSH(PC & 0xff); SET_BREAK(1);
// PUSH(SR); SET_INTERRUPT(1);
0: begin n <= 1; cp <= {8'h01, s}; W <= 1; s <= s - 1; D <= pc[15:8]; m <= 1; end
1: begin n <= 2; cp[7:0] <= s; W <= 1; s <= s - 1; D <= pc[7:0]; p[BF] <= 1; p[5] <= 1; end
2: begin n <= 3; cp[7:0] <= s; W <= 1; s <= s - 1; D <= p; p[IF] <= 1; end
// LOAD ADDR
3: begin n <= 4; cp <= {12'hFFF, 1'b1, intr, 1'b0}; end
4: begin n <= 5; cp[0] <= 1; tr <= I; end
5: begin m <= 0; t <= LOAD; pc <= {I, tr}; end
 
endcase
 
// [6T] Jump Subroutine
JSR: case (n)
 
0: begin n <= 1; tr <= I; pc <= pcn; cp[15:8] <= 8'h01; end
1: begin n <= 2; cp[7:0] <= s; s <= s - 1; D <= pc[15:8]; W <= 1; pc[15:8] <= I; m <= 1; end
2: begin n <= 3; cp[7:0] <= s; s <= s - 1; D <= pc[ 7:0]; W <= 1; end
3: begin n <= 4; pc[7:0] <= tr; m <= 0; end
4: begin t <= LOAD; end
 
endcase
 
// [6T] Возврат из процедуры
RTS: case (n)
 
0: begin n <= 1; m <= 1; cp[7:0] <= s + 1; s <= s + 1; cp[15:8] <= 8'h01; end
1: begin n <= 2; pc[7:0] <= I; cp[7:0] <= s + 1; s <= s + 1; end
2: begin n <= 3; pc <= {I, pc[7:0]} + 1; m <= 0; end
3: begin n <= 4; end
4: begin t <= LOAD; end
 
endcase
 
// [6T] Возврат из прерывания
RTI: case (n)
 
0: begin n <= 1; cp[7:0] <= s + 1; s <= s + 1; cp[15:8] <= 8'h01; m <= 1; end
1: begin n <= 2; cp[7:0] <= s + 1; s <= s + 1; p <= I; end
2: begin n <= 3; cp[7:0] <= s + 1; s <= s + 1; pc[7:0] <= I; end
3: begin n <= 4; pc[15:8] <= I; m <= 0; end
4: begin t <= LOAD; end
 
endcase
 
endcase
 
end
 
endmodule