Перевод этой страницы:
ru

6502

Процессор 6502 был разработан компанией MOS в 197-бородатом году. За основу была положена архитектура процессора Motorolla 6800:

6502_die_shot.jpg 6800.jpg
(видно что в обоих случаях верхнюю часть занимает декодер и рандомная логика, а всю нижнюю часть процессора занимает контекст)

Описание внутренней архитектуры процессора 6502

Процессор делится на 2 части : верхняя и нижняя.

В верхней части находится управляющая часть, которая выдает ряд контрольных линий ("команд") для управления нижней частью.
В нижней части находится контекст процессора : внутренние шины и регистры, с одним исключением - регистр флагов (P) находится в верхней части в "размазанном" виде.

Работа процессора тактируется тактовым импульсом PHI0, при этом используются оба полутакта. Во время первого полутакта (PHI1) процессор находится в режиме "говорю". В это время процессор выдает наружу данные.
Во время второго полутакта (PHI2) процессор находится в режиме "слушаю", во время этого полутакта внешние устройства могут помещать данные на шину данных, чтобы процессор их "обработал".

Регистры

  • PD : текущее значения кода операции для предекодирования
  • IR : регистр инструкций (хранит код текущей операции)
  • X, Y : индексные регистры
  • S: указатель стека
  • AI, BI : входные значения для АЛУ
  • ADD : промежуточный результат операции на АЛУ
  • AC : аккумулятор
  • PCH/PCL : program counter, состоящий из 2-х половинок
  • PCHS/PCLS : вспомогательные регистры program counter (S означает "set" (?))
  • ABH/ABL : регистры для вывода на внешнюю шину адреса
  • DL : data latch, хранит последнее прочитанное значение внешней шины данных
  • DOR : data output register, содержит значение которое будет записано в шину данных
  • P : регистр флагов, на самом деле состоит из множества разбросанных по схеме защелок

Непосредственно программисту доступны следующие регистры : A (аккумулятор), X, Y, S, P, PC.

Внешние шины

Внешних шин всего две : 16-разрядная адресная (ADDR) и 8-разрядная шина данных (DATA). Адресная шина односторонняя - писать в неё может только процессор. Шина данных двунаправленная.

Внутренние шины

  • ADH/ADL : шина адреса
  • SB : special bus, шина для обмена регистрами
  • DB : внутренняя шина данных

Во время второго полутакта (PHI2) все внутренние шины подзаряжаются и имеют значение 0xff. Сделано это по причине того, что "разрядить" транзистор в нужный момент получается быстрее, чем "зарядить" (смена значения 1⇒0 происходит быстрее, чем смена 0⇒1).

Соединения регистров с шинами

6502_context.jpg

Последовательно соединяя шины и регистры процессор выполняет разнообразные инструкции. Многообразие соединений обеспечивает разнообразие команд процессора, а разделение команд на такты позволяет выполнять сложные действия. Дополнительно производится управление АЛУ (сложение, логическое-И и пр.)

Режимы адресации 6502

Адресация - это способ доставить операнд в нужное место памяти (или загрузить его оттуда). Разработчики 6502 были очень щедрыми и добавили в контекст ажно два индексных регистра X и Y.

"Индексный" - это означает что к адресу памяти определенным образом добавляется смещение, чтобы получить новый адрес. Обычно это нужно для доступу к массивам. В этом случае начало массива будет являться фиксированным адресом, а значение в индексном регистре - индексом массива (смещением).

Список режимов адресации :

  • Immediate (непосредственный операнд). В этом случае операнд хранится в самой инструкции (обычно вторым байтом, после кода операции). Пример LDA #$1C : A = 0x1C
  • Absolute (абсолютная адресация). В инструкции указывается полный 16-разрядный адрес, откуда следует получить операнд. Например LDA $1234 : A = [$1234]
  • Zero page absolute (абсолютная адресация на нулевой странице) : Разработчики сделали оптимизированную версию абсолютной адресации, добавив возможность адресоваться только к нулевой странице (страницы 6502 имеют размер 256 байт). Пример LDA $56 : в этом случае процессор автоматом делает старшие 8 разрядов адреса равными 0x00, а младшие 8 разрядов берутся из инструкции. Итоговый адрес получается 0x0056. A = [0x0056]. Сделано это для экономии размера инструкции (экономится 1 байт).
  • Indexed (индексная) : в этом режиме адресации к постоянному значению адреса добавляется смещение из регистра X или Y. Например LDA $1234, X : A = [$1234 + X]
  • Zero page Indexed (индексная адресация на нулевой странице) : аналогично индексной, но использовать можно только регистр X. Пример LDA $33, X : A = [$0033 + X]

Ну а дальше начинается особенная магия :

  • Pre-indexed indirect (косвенная с пре-индексацией) : Значение операнда, который является адресом в нулевой странице складывается со значением регистра X и получается косвенный адрес. Затем по адресу, на который ссылается косвенный адрес, получается значение операнда. Пример LDA ($34, X) : A = [[$0034 + X]]. Важно : при сложении адреса и значения в регистре X происходит "заворачивание" вокруг 256 байт. То есть перенос в старшую половину адреса не происходит. ( 0xFF + 0x02 будет равно 0x0001, а не 0x0101). Косвенная означает "взять адрес по адресу".
  • Post-indexed indirect (косвенная с пост-индексацией) : Отличается от предыдущей тем, что вначале выбирается косвенный адрес из нулевой страницы, а затем к нему добавляется значение индексного регистра Y. Пример LDA ($2A), Y : A = [[$002A] + Y].

Набор инструкций

6502 обладает всеми необходимыми инструкциями, а также включает в себя такие достаточно удобные инструкции как ротация бит (ROL/ROR) и тестирование разряда (BIT). Не все процессоры того времени содержали такие операции.

Поведение инструкций, а также адресные режимы полностью задаются кодом операции, для упрощения декодирования, однако ширина шины (8 разрядов) не позволяет выполнять все инструкции за 1 такт. Также декодер несколько не оптимизирован, поэтому минимальное время выполнения инструкций - 2 такта, при этом первый такт всегда занимает выборка кода операции (1й байт инструкции).

Краткое содержание инструкций :

ИнструкцияДействие
ADC Add Memory to Accumulator with Carry
AND "AND" Memory with Accumulator
ASL Shift Left One Bit (Memory or Accumulator)
BCC Branch on Carry Clear
BCS Branch on Carry Set
BEQ Branch on Result Zero
BIT Test Bits in Memory with Accumulator
BMI Branch on Result Minus
BNE Branch on Result not Zero
BPL Branch on Result Plus
BRK Force Break
BVC Branch on Overflow Clear
BVS Branch on Overflow Set
CLC Clear Carry Flag
CLD Clear Decimal Mode
CLI Clear interrupt Disable Bit
CLV Clear Overflow Flag
CMP Compare Memory and Accumulator
CPX Compare Memory and Index X
CPY Compare Memory and Index Y
DEC Decrement Memory by One
DEX Decrement Index X by One
DEY Decrement Index Y by One
EOR "Exclusive-Or" Memory with Accumulator
INC Increment Memory by One
INX Increment Index X by One
INY Increment Index Y by One
JMP Jump to New Location
JSR Jump to New Location Saving Return Address
LDA Load Accumulator with Memory
LDX Load Index X with Memory
LDY Load Index Y with Memory
LSR Shift Right One Bit (Memory or Accumulator)
NOP No Operation
ORA "OR" Memory with Accumulator
PHA Push Accumulator on Stack
PHP Push Processor Status on Stack
PLA Pull Accumulator from Stack
PLP Pull Processor Status from Stack
ROL Rotate One Bit Left (Memory or Accumulator)
ROR Rotate One Bit Right (Memory or Accumulator)
RTI Return from Interrupt
RTS Return from Subroutine
SBC Subtract Memory from Accumulator with Borrow
SEC Set Carry Flag
SED Set Decimal Mode
SEI Set Interrupt Disable Status
STA Store Accumulator in Memory
STX Store Index X in Memory
STY Store Index Y in Memory
TAX Transfer Accumulator to Index X
TAY Transfer Accumulator to Index Y
TSX Transfer Stack Pointer to Index X
TXA Transfer Index X to Accumulator
TXS Transfer Index X to Stack Pointer
TYA Transfer Index Y to Accumulator

Разработчики подбирали кодировку таким образом, чтобы её было удобней обрабатывать декодером и рандомной логикой. Более подробное описание каждой инструкции вы можете узнать перейдя по ссылке соотв. кода операции.

Прерывания

Всего у 6502 существует четыре типа прерываний:

  • IRQ: обычное аппаратное прерывание. Можно запретить флагом I (interrupt disable), если флаг I=1, это значит что прерывание "отключено" и не проходит в процессор.
  • NMI: немаскируемое прерывание. Имеет более высокий приоритет над IRQ, срабатывает по перепаду уровня, конкретно - по спаду (falling edge).
  • RES: аппаратный сброс. После включения 6502 нужно устанавливать контакт /RES в 0 в течении нескольких тактов, чтобы процессор "пришёл в себя".
  • BRK: программное прерывание. Инициируется инструкцией BRK.

Verilog

// ------------------
// Dispatcher
 
module Dispatcher (
    // Outputs
    _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC, ACRL2, 
    // Inputs
    PHI0, RDY,
    DORES, RESP, B_OUT, BRK6E, BRFW, _BRTAKEN, ACR, _ADL_PCL, PC_DB, _IMPLIED, _TWOCYCLE, 
    decoder
);
 
    input PHI0, RDY;
    input DORES, RESP, B_OUT, BRK6E, BRFW, _BRTAKEN, ACR, _ADL_PCL, PC_DB, _IMPLIED, _TWOCYCLE;
    input [129:0] decoder;
 
    output _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC, ACRL2;
    wire _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC;
 
    // Clocks
    wire PHI1, PHI2;
    assign PHI1 = ~PHI0;
    assign PHI2 = PHI0;
 
    // Misc
    wire BR2, BR3, _MemOP, STOR, _SHIFT, _STORE;
    assign BR2 = decoder[80];
    assign BR3 = decoder[93];
    assign _MemOP = ~( decoder[111] | decoder[122] | decoder[123] | decoder[124] | decoder[125] );
    assign STOR = ~( ~decoder[97] | _MemOP );
    assign _SHIFT = ~( decoder[106] | decoder[107] );
    assign _STORE = ~decoder[97];
 
    // Ready Control
    wire Ready1_Out, Ready2_Out;
    mylatch Ready1 ( Ready1_Out, ~(RDY | Ready2_Out), PHI2 );
    mylatch Ready2 ( Ready2_Out, WR, PHI1 );
    assign _ready = Ready1_Out;
 
    // R/W Control
    wire WRLatch_Out, RWLatch_Out;
    wire WR;
    mylatch WRLatch ( WRLatch_Out, ~( decoder[98] | decoder[100] | T5 | STOR | T6 | PC_DB), PHI2 );
    assign WR = ~ ( _ready | REST | WRLatch_Out );
    mylatch RWLatch ( RWLatch_Out, WR, PHI1 );
    assign RW = ~RWLatch_Out;
    assign RD = (PHI1 | ~RWLatch_Out);
 
    // Short Cycle Counter (T0-T1)
    wire TRESXLatch_Out, TWOCYCLELatch_Out, TRES1Latch_Out, T0Latch_Out, T1Latch_Out;
    mylatch TRESXLatch ( TRESXLatch_Out, TRESX, PHI1 );
    mylatch TWOCYCLELatch ( TWOCYCLELatch_Out, _TWOCYCLE, PHI1 );
    mylatch TRES1Latch ( TRES1Latch_Out, TRES1, PHI1 );
    assign _T0X = ~( (~(TRESXLatch_Out&TWOCYCLELatch_Out) & ~TRES1Latch_Out) | ~(T0Latch_Out | T1Latch_Out) );
    assign T0 = ~_T0X;
    mylatch T0Latch ( T0Latch_Out, _T0X, PHI2 );
    mylatch T1Latch ( T1Latch_Out, ~(T0Latch_Out | _ready), PHI1 );
    assign _T1X = ~T1Latch_Out;
 
    // Long Cycle Counter (T2-T5) (Shift Register)
    wire T1InputLatch_Out;
    mylatch T1InputLatch ( T1InputLatch_Out, T1, PHI2 );
 
    wire LatchIn_T2_Out, LatchOut_T2_Out;
    mylatch LatchIn_T2 ( LatchIn_T2_Out, _ready ? LatchOut_T2_Out : ~T1InputLatch_Out, PHI1 );
    mylatch LatchOut_T2 ( LatchOut_T2_Out, ~(LatchIn_T2_Out | TRES2), PHI2 );
    assign _T2 = (LatchIn_T2_Out | TRES2 );
 
    wire LatchIn_T3_Out, LatchOut_T3_Out;
    mylatch LatchIn_T3 ( LatchIn_T3_Out, _ready ? LatchOut_T3_Out : ~LatchOut_T2_Out, PHI1 );
    mylatch LatchOut_T3 ( LatchOut_T3_Out, ~(LatchIn_T3_Out | TRES2), PHI2 );
    assign _T3 = (LatchIn_T3_Out | TRES2 );
 
    wire LatchIn_T4_Out, LatchOut_T4_Out;
    mylatch LatchIn_T4 ( LatchIn_T4_Out, _ready ? LatchOut_T4_Out : ~LatchOut_T3_Out, PHI1 );
    mylatch LatchOut_T4 ( LatchOut_T4_Out, ~(LatchIn_T4_Out | TRES2), PHI2 );
    assign _T4 = (LatchIn_T4_Out | TRES2 );
 
    wire LatchIn_T5_Out, LatchOut_T5_Out;
    mylatch LatchIn_T5 ( LatchIn_T5_Out, _ready ? LatchOut_T5_Out : ~LatchOut_T4_Out, PHI1 );
    mylatch LatchOut_T5 ( LatchOut_T5_Out, ~(LatchIn_T5_Out | TRES2), PHI2 );
    assign _T5 = (LatchIn_T5_Out | TRES2 );
 
    // Extra Cycle Counter (T5-T6)
    wire T56Latch_Out, T5Latch1_Out, T2Latch2_Out, T6Latch1_Out, T6Latch2_Out;
    mylatch T56Latch ( T56Latch_Out, ~(_SHIFT | _MemOP | _ready), PHI2 );
    mylatch T5Latch1 ( T5Latch1_Out, ~(T5Latch2_Out & _ready) & ~T56Latch_Out, PHI1 );
    mylatch T5Latch2 ( T5Latch2_Out, ~T5Latch1_Out, PHI2 );
    mylatch T6Latch1 ( T6Latch1_Out, ~(~T5Latch1_Out & ~_ready), PHI2 );
    mylatch T6Latch2 ( T6Latch2_Out, ~T5Latch1_Out, PHI1 );
    assign T5 = ~T5Latch1_Out;
    assign T6 = T6Latch2_Out;
 
    // Instruction Termination (reset cycle counters)
    wire REST, ENDS, ENDX, TRES2;
    wire ENDS1_Out, ENDS2_Out;
    assign REST = ~(_STORE & _SHIFT) & DORES;
 
    mylatch ENDS1 ( ENDS1_Out, _ready ? ~T1 : (~(_BRTAKEN & BR2) & ~T0), PHI2 );
    mylatch ENDS2 ( ENDS2_Out, RESP, PHI2 );
    assign ENDS = ~( ENDS1_Out | ENDS2_Out );
 
    wire temp;
    assign temp = ~( decoder[100] | decoder[101] | decoder[102] | decoder[103] | decoder[104] | decoder[105]);
    assign ENDX = ~( ~temp | T6 | BR3 | ~(_MemOP | decoder[96] | ~_SHIFT) );
 
    wire ReadyPhi1_Out, RESP1_Out, RESP2_Out, T1L_Out;
    mylatch ReadyPhi1 ( ReadyPhi1_Out, ~_ready, PHI1 );
    mylatch RESP1 ( RESP1_Out, ~(RESP | ReadyPhi1_Out | RESP2_Out), PHI2 );
    mylatch RESP2 ( RESP2_Out, ~(RESP1_Out | Brfw), PHI1 );
    mylatch T1L ( T1L_Out, ~TRES1, PHI1 );
    assign T1 = ~T1L_Out;
    assign TRES1 = (ENDS | ~(_ready | ~(RESP1_Out | Brfw) ) );
    assign SYNC = T1;
 
    wire TRESX1_Out, TRESX2_Out;
    mylatch TRESX1 ( TRESX1_Out, ~(decoder[91] | decoder[92]), PHI2);
    mylatch TRESX2 ( TRESX2_Out, ~( RESP | ENDS | ~(_ready | ENDX) ), PHI2 );
    assign TRESX = ~(BRK6E | ~(_ready | ACRL1 | REST | TRESX1_Out) | ~TRESX2_Out);
 
    wire TRES2Latch_Out;
    mylatch TRES2Latch ( TRES2Latch_Out, TRESX, PHI1 );
    assign TRES2 = ~TRES2Latch_Out;
 
    // ACR Latch
    wire ACRL1, ACRL2;
    wire ACRL1Latch_Out, ACRL2Latch_Out;
    assign ACRL2 = ~(~ACR & ~ReadyDelay) & (~ReadyDelay | ~ACRL1Latch_Out);
    mylatch ACRL1Latch ( ACRL1Latch_Out, ~ACRL2Latch_Out, PHI2 );
    mylatch ACRL2Latch ( ACRL2Latch_Out, ACRL2, PHI1 );
    assign ACRL1 = ~ACRL1Latch_Out;
 
    // Program Counter Increment Control
    wire ReadyDelay, Brfw;
 
    wire DelayLatch1_Out, DelayLatch2_Out;
    mylatch DelayLatch1 ( DelayLatch1_Out, _ready, PHI1 );
    mylatch DelayLatch2 ( DelayLatch2_Out, ~DelayLatch1_Out, PHI2 );
    assign ReadyDelay = ~DelayLatch2_Out;
 
    wire BRFWLatch_Out;
    mylatch BRFWLatch ( BRFWLatch_Out, ~(~BR3 | ReadyDelay), PHI2 );
    assign Brfw = ~(BRFW ^ ACR) & BRFWLatch_Out;
 
    wire RouteCLatch_Out, a_out, b_out, c_out;
    mylatch RouteCLatch ( RouteCLatch_Out, ~(BR2 & _BRTAKEN) & (_ADL_PCL | BR2 | BR3), PHI2 );
    mylatch c_latch ( c_out, ~(RouteCLatch_Out | _ready | ~_IMPLIED), PHI1 );
    mylatch a_latch ( a_out, B_OUT, PHI1 );
    mylatch b_latch ( b_out, Brfw, PHI1 );
    assign _IPC = ~(a_out & (b_out | c_out));
 
    // Fetch Control
    wire FetchLatch_Out;
    mylatch FetchLatch ( FetchLatch_Out, T1, PHI2 );
    assign FETCH = ~( _ready | ~FetchLatch_Out );
    assign Z_IR = ~( B_OUT & FETCH );
 
endmodule   // Dispatcher
6502.txt · Последние изменения: 2015/09/03 23:15 — org
 
За исключением случаев, когда указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: Public Domain
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki