А как наличие общего счетчика может пострадать от перевода с двухфазной модели на однофазную синхронную?
Вид для печати
А как наличие общего счетчика может пострадать от перевода с двухфазной модели на однофазную синхронную?
Убедил :) самого себя
А я бы начал с инициализации сего хитрого девайса. Это очевидно для профи. А чайники потом будут репу чухать. Тем более будет не лишним описать с чего, все стартует. И о блоках(чипа) немного.
Вроде по делу. Простите за мой ломаный английский.
Сегодня мы разберем системные регистры. Это два регистра, с индексами 0 и 1, которые задают режим работы всего PPU. Собственно, регистры как регистры, сохраняют входящие от CPU данные, синхронизируют их с ядром PPU и выдают их этому ядру. Назначение бит этих регистров следующее:
Регистр #0:
D7 - VBL Разрешение генерации на ноге #19 сигнала низкого уровня после отрисовки растра, которая подключена ко входу NMI CPU (прерывание VBlank).
D6 --- Отсутствует
D5 - O8_16 Высота спрайтов, если 0 то 8 точек, если 1 то 16 точек.
D4 - BGSEL Старший бит адреса знакогенератора для тайлов фона (0ххх/1xxx).
D3 - OBSEL Cтарший бит адреса знакогенератора для спрайтов (0xxx/1xxx).
D2 - I1_32 Приращение адреса при достпуе к VRAM: 0 - 1, 1 - 32 (для рисования горизонтальных или вертикальных линий).
D1, D0 Выбор старшего адреса для таблицы имен тайлов фона (номер экрана), эти биты обрабатываются в формирователе адреса VRAM.
Регистр #1:
D7 - EmB Приглушение синих оттенков, буржуи называют это эмпфазисом, подробнее о нем будет при разборе видео ЦАП.
D6 - EmG Приглушение зеленых оттенков, буржуи называют это эмпфазисом, подробнее о нем будет при разборе видео ЦАП.
D5 - EmR Приглушение красных оттенков, буржуи называют это эмпфазисом, подробнее о нем будет при разборе видео ЦАП.
D4 - OBE Включение отображения спрайтов.
D3 - BGE Включение отображения фона.
D2 - OBCLIP Гашение столбца из левых 8 точек на плоскости спрайтов.
D1 - BGCLIP Гашение столбца из левых 8 точек на плоскости фона.
D0 - B_W Отключение вывода оттенков (Ч/Б вывод изображения).
Оригинальная схема "влоб":
http://savepic.net/10201183m.png
Оптимизированная синхронная схема:
http://savepic.net/10212447m.png
Функциональный аналог Verilog:
Обратите внимание, что я не использую тактовый сигнал PClk (позитивный пиксельклок) как сигнал чувствительности always блока. Промежуточная модель его именно так и использовала, но в строго синхронной модели этого быть не должно. Тактовая частота должна быть одна во всем дизайне. PClk же становится сигналом разрешения, длительностью строго в 1 такт главной тактовой частоты Clk и периодичностью нужные нам 4 такта для NTSC PPU (или 5 тактов для PAL PPU). Эта же тенденция будет сохраняться и для остальных блоков.Код:// Системные регистры 0 / 1
module PPU_SYS_REGS(
// Системные входы
input Clk, // Тактовая частота
input PClk, // Пиксельклок
// Управляющие входы
input W0, // Запись в регистр 0
input W1, // Запись в регистр 1
input [7:0]DBIN, // Данные
// Выходы
output reg I1_32, // Увеличение адреса +1/+32 (H/V)
output reg OBSEL, // Старший бит адреса знакогенератора спрайтов
output reg BGSEL, // Старший бит адреса знакогенератора фона
output reg O8_16, // Высота спрайтов (0 - 8 точек, 1 - 16 точек)
output reg VBL, // Разрешение прерывания VBlank
output reg B_W, // Режим Ч/Б (обнуление младших 4х битов индекса цвета)
output reg BGCLIP, // Гашение левого столбца 8 точек у фона
output reg OBCLIP, // Гашение левого столбца 8 точек у спрайтов
output reg BGE, // Включение фона
output reg OBE, // Включение спрайтов
output reg EmR, // Эмпфазис красного цвета
output reg EmG, // Эмпфазис зеленого цвета
output reg EmB // Эмпфазис синего цвета
);
// Промежуточные регистры
reg [4:0]W0R;
reg [7:0]W1R;
// Логика
always @(posedge Clk) begin
if (PClk) begin
// Запись значения в промежуточные регистры
if (W0) W0R[4:0] <= {DBIN[7],DBIN[5:2]};
if (W1) W1R[7:0] <= DBIN[7:0];
// Обновление значений
if (~W0) {VBL,O8_16,BGSEL,OBSEL,I1_32} <= W0R[4:0];
if (~W1) {EmB,EmG,EmR,OBE,BGE,OBCLIP,BGCLIP,B_W} <= W1R[7:0];
end
end
// Конец
endmodule
Savepic сдох. Прежде чем перейти к следующему блоку, я попытаюсь восстановить картинки.
Сэйвпик ожил, дублирую фотки себе, в теме буду хостить у себя на всякий случай.
Руки дошли, схоронил все картинки. Готовлю следующую тему по мере сил и времени.
Давно я ничего сюда не постил, а время то летит....
Блоки, входными сигналами которых были только внешние сигналы, закончились. Сегодня поговорим о сердце PPU: о синхрогенераторе. Это "сердце" PPU, он дирижирует остальными блоками.
Входные сигналы:
Clk - Главный тактовый сигнал
OBCLIP - Обрезание левой части экрана спрайтов
BGCLIP - Обрезание левой части экрана фона
BGE - Активация фона
OBE - Активация спрайтов
VBL - Разрешение запроса прерывания VBlank
R2 - Чтение регистра 2
nRES - Общий сброс PPU
Выходные сигналы:
PClk - Пиксельклок
ALEGate - Гейт ALE
[5:0]HNN - Синхронизированное атомарное состояние PPU
S_EV - Запуск процесса просмотра списка спрайтов
CLIP_O - Гашение левого столбца из 8ми точек экрана для спрайтов
CLIP_B - Гашение левого столбца из 8ми точек экрана для фона
O_HPOS - Запуск счетчиков координаты X спрайтов (позиция 0 спрайтов)
EVAL - Сброс счетчика OAMT и начало процесса обработки OAMT
E_EV - Инициализация схемы адреса для вычитания графики спрайтов
I_OAM2 - Сигнал очистки OAMT
PAR_O - Вычитывание графики спрайтов
VIS - Маска активного растра (графика выводится на экран)
F_NT - Чтение Name Table
F_TB - Чтение второго байта
F_TA - Чтение первого байта
N_FO - Сигнал разрешения вывода графики фона
F_AT - Чтение атрибутов из Name Table
SC_CNT - Запуск счетчика адресов при включении растра и/или фона
BURST - Маска вывода вспышки синхронизации поднесущей цвета
SYNC - Синхросмесь видеосигнала
PICTURE - Маска выводимого изображения
N_INT - NMI прерывание по VBLANK
R2B7 - Чтение флага NMI
BLNK - Гашение
RESCL - Строка пререндера (сброс всех схем выборки)
[7:0]V - Выход вертикального счетчика (для спрайтовой машины)
HSYNC - Выход строчной синхронизации
VSYNC - Выход кадровой синхронизации
Синхрогенератор содержит 2 счетчика: горизонтальный и вертикальный. Горизонтальный считает пиксели (по пиксельклоку) а вертикальный - строки растра. Каждый сигнал имеет четкую координату Пиксель:Строка, которая в оригинальной схеме задана обычной матрицей:
http://savepic.net/10175377.png
http://savepic.net/10179473.png
Я не буду более детально расписывать каждый вырабатываемый сигнал. Это лучше сделать при описании модулей, где конкретный сигнал используется.
По традиции, схема "влоб":
http://savepic.net/10188703m.jpg
Оптимизированная схема:
http://savepic.net/10178463m.jpg
Verilog код:
Код:// Главный счетчик таймингов PPU
module PPU_HV_COUNTER(
// Системные входы
input Clk, // Главные такты
// Входные цепи
input OBCLIP, // Обрезание левой части экрана спрайтов
input BGCLIP, // Обрезание левой части экрана фона
input BGE, // Активация фона
input OBE, // Активация спрайтов
input VBL, // Разрешение запроса прерывания VBlank
input R2, // Чтение регистра 2
input nRES, // Общий сброс PPU
// Выходные цепи
output reg PClk, // Пиксельклок
output reg ALEGate, // Гейт ALE
output reg [5:0]HNN, // Синхронизированное атомарное состояние PPU
output reg S_EV, // Запуск процесса просмотра списка спрайтов
output reg CLIP_O, // Гашение левого столбца из 8ми точек экрана для спрайтов
output reg CLIP_B, // Гашение левого столбца из 8ми точек экрана для фона
output reg O_HPOS, // Запуск счетчиков координаты X спрайтов (позиция 0 спрайтов)
output reg EVAL, // Сброс счетчика OAMT и начало процесса обработки OAMT
output reg E_EV, // Инициализация схемы адреса для вычитания графики спрайтов
output reg I_OAM2, // Сигнал очистки OAMT
output reg PAR_O, // Вычитывание графики спрайтов
output reg VIS, // Маска активного растра (графика выводится на экран)
output reg F_NT, // Чтение Name Table
output reg F_TB, // Чтение второго байта
output reg F_TA, // Чтение первого байта
output reg N_FO, // Сигнал разрешения вывода графики фона
output reg F_AT, // Чтение атрибутов из Name Table
output SC_CNT, // Запуск счетчика адресов при включении растра и/или фона
output reg BURST, // Маска вывода вспышки синхронизации поднесущей цвета
output reg SYNC, // Синхросмесь видеосигнала
output PICTURE, // Маска выводимого изображения
output N_INT, // NMI прерывание по VBLANK
output reg R2B7, // Чтение флага NMI
output BLNK, // Гашение
output reg RESCL, // Строка пререндера (сброс всех схем выборки)
output [7:0]V, // Выход вертикального счетчика (для спрайтовой машины)
output reg HSYNC, // Выход строчной синхронизации
output reg VSYNC // Выход кадровой синхронизации
);
// Переменные
reg [1:0]ClkDiv; // Предделитель тактов
reg [8:0]HCnt; // Строчный счетчик
reg [8:0]VCnt; // Кадровый счетчик
reg V8R; //
reg Odd; // Чет/нечет
reg VB; // Вертикальное
reg FPORCH;
reg BPORCH;
reg N_HB;
reg BREN;
reg VSNC;
reg PEN;
reg [2:0]IFIFO;
reg IREQ;
reg R2R;
reg BLNKR;
// Комбинаторика
assign SC_CNT = ~N_HB & (BGE | OBE);
assign PICTURE = ~PEN & ~BPORCH;
assign N_INT = ~(IREQ & VBL);
assign BLNK = BLNKR | ~(BGE | OBE);
assign V[7:0] = VCnt[7:0];
// Логика
always @(posedge Clk) begin
// Синхронизация
R2R <= R2; V8R <= VCnt[8];
// Запрос прерывания
if (~IFIFO[0] & ~IFIFO[2]) IREQ <= 1'b1;
else if (R2R | RESCL) IREQ <= 1'b0;
// Флаг прерывания
if (~R2R & ~R2) R2B7 <= IREQ;
// Флаг Odd
if (V8R & ~VCnt[8]) Odd <= ~Odd;
// Делитель частоты
ClkDiv[1:0] <= ClkDiv[1:0] + 2'h1;
// Активация пиксельклока строго на 1 такт
PClk <= ClkDiv[1] & ClkDiv[0];
// Маска сигнала ALE
ALEGate <= ~ClkDiv[1];
// Пиксельклок
if (PClk) begin
// Синхронизация
HNN[5:0] <= HCnt[5:0];
// Счетчик строк
if (~nRES | (HCnt[8:0] == 9'd340) | (Odd & (HCnt[8:0] == 9'd339) & RESCL)) begin
// Строка досчитала, обрабатываем
HCnt[8:0] <= 9'd000;
// Счетчик кадров
if (~nRES | (VCnt[8:0] == 9'd261)) VCnt[8:0] <= 9'd000; else VCnt[8:0] <= VCnt[8:0] + 9'd001;
end else HCnt[8:0] <= HCnt[8:0] + 9'd001;
// Сигнал N_FO - Переключение между графикой и служебной информацией
N_FO <= ~(BLNK | (HCnt[8] & (~HCnt[6] | HCnt[5] | HCnt[4])));
// Сигнал S_EV
S_EV <= ~BLNK & (HCnt[8:0] == 9'd065);
// Сигналы отсечения
CLIP_O <= ~OBCLIP & (~(HCnt[8] | VB) & ~HCnt[7] & ~HCnt[6] & ~HCnt[5] & ~HCnt[4] & ~HCnt[3]);
CLIP_B <= ~BGCLIP & (~(HCnt[8] | VB) & ~HCnt[7] & ~HCnt[6] & ~HCnt[5] & ~HCnt[4] & ~HCnt[3]);
// Сигнал позиции
O_HPOS <= ~BLNK & (HCnt[8:0] == 9'd339);
// Сигнал EVAL
EVAL <= ~BLNK & ((HCnt[8:0] == 9'd339) | (HCnt[8:0] == 9'd063) | (HCnt[7:0] == 8'd255));
// Сигнал E_EV
E_EV <= ~BLNK & (HCnt[7:0] == 8'd255);
// Сигнал I_OAM2
I_OAM2 <= ~BLNK & ~HCnt[8] & ~HCnt[7] & ~HCnt[6];
// Сигнал PAR_O
PAR_O <= ~BLNK & HCnt[8] & ~HCnt[7] & ~HCnt[6];
// Сигнал VIS
VIS <= ~VB & ~HCnt[8] & ~BLNK;
// Сигнал F_NT - Чтение таблицы символов
F_NT <= ~BLNK & ~HCnt[2] & ~HCnt[1];
// Сигнал F_TB - Чтение второго байта графики
F_TB <= ~(BLNK | (HCnt[8] & (~HCnt[6] | HCnt[5] | HCnt[4]))) & HCnt[2] & HCnt[1];
// Сигнал F_TA - Чтение первого байта графики
F_TA <= ~(BLNK | (HCnt[8] & (~HCnt[6] | HCnt[5] | HCnt[4]))) & HCnt[2] & ~HCnt[1];
// Сигнал F_AT - Чтение атрибутов
F_AT <= ~(BLNK | (HCnt[8] & (~HCnt[6] | HCnt[5] | HCnt[4]))) & ~HCnt[2] & HCnt[1];
// Сигнал HSYNC
HSYNC <= FPORCH;
// Сигнал VSYNC
VSYNC <= ~VSNC & ~N_HB;
// Сигнал SYNC
SYNC <= FPORCH | (~VSNC & ~N_HB);
// Сигнал BURST
BURST <= BREN & (FPORCH | (~VSNC & ~N_HB));
// Сигнал PEN
if (VCnt[7:0] == 8'd241) PEN <= 1'b1;
else if (VCnt[8:0] == 9'd261) PEN <= 1'b0;
// Сигнал BPORCH
if (HCnt[8:0] == 9'd270) BPORCH <= 1'b1;
else if (HCnt[8:0] == 9'd328) BPORCH <= 1'b0;
// Сигнал VB
if (VCnt[8:0] == 9'd261) VB <= 1'b0;
else if (VCnt[7:0] == 8'd239) VB <= 1'b1;
// Сигнал BLNKR
if (VCnt[8:0] == 9'd261) BLNKR <= 1'b0;
else if (VCnt[7:0] == 8'd240) BLNKR <= 1'b1;
// Сигнал FPORCH
if (HCnt[8:0] == 9'd256) FPORCH <= 1'b1;
else if (HCnt[8:0] == 9'd279) FPORCH <= 1'b0;
// Сигнал N_HB
if (HCnt[8:0] == 9'd279) N_HB <= 1'b1;
else if (HCnt[8:0] == 9'd304) N_HB <= 1'b0;
// Сигнал BREN
if (HCnt[8:0] == 9'd308) BREN <= 1'b1;
else if (HCnt[8:0] == 9'd323) BREN <= 1'b0;
// Сигнал VSNC
if (N_HB) begin
if (VCnt[8:0] == 9'd244) VSNC <= 1'b1;
else if (VCnt[8:0] == 9'd247) VSNC <= 1'b0; // FIFO запроса прерывания
end
IFIFO[2:0] <= {~IFIFO[1],IFIFO[0],~(VCnt[7:0] == 8'd241)};
// Сигнал RESCL
RESCL <= (VCnt[8:0] == 9'd261);
end
end
// Конец
endmodule
Блок формирования управляющих сигналов локальной шины PPU.
Локальная шина PPU это отдельное адресное пространство для памяти, которая хранит текущее состояние картинки. Она состоит из младшего байта адреса и данных, мультиплексированных между собой в сигналы PPU_AD[7..0], старших 6ти бит адреса PPU_A[13..8] и сигналов управления: PPU_ALE для защелкивания адреса на шине PPU_AD[7..0], ~PPU_RD для строба чтения данных и ~PPU_WR для строба записи данных. Направление указано со стороны PPU. Для упрощения, я их буду называть так: PAD[7..0], PA[13..8], ALE, PRD и PWR (на схемах RD и WR соответственно).
- 2 знакогенератора, младшие 8 КБайт: 0000-0FFF и 1000-1FFF.
- 4 т.н. "таблицы имён" (Name Tables, это название появилось из-за тайлового метода формирования изображения PPU: в "таблице имен" заносится код символа из знакогенератора, который потом и отображается на текущем месте тайла, совсем как текст у ВГ75), по 1 КБайту на страницу: 2000-23FF, 2400-27FF, 2800-2BFF и 2C00-2FFF. В каждой странице есть сама страница тайлов x000-x3BF и таблица атрибутов 0x3C0-x3FF.
- 4 палитры фона: 3F00-3F0F
- 4 палитры спрайтов: 3F10-3F1F
Остальные неуказанные области просто не используются. Причем, так как ОЗУ палитры находится внутри PPU то генерация внешних сигналов PRD и PWR не производится, при обращении к области 3F00-3FFF, но область 3000-3EFF программно доступна через регистр #7. Вот схема "влоб":
http://savepic.net/10200834m.jpg
Сигналы обращения к регистру #7 проходят через синхронизатор и формирователь нужного тайминга для локальной шины PPU. Каждое обращение к регистру #7 формирует строб увеличения счетчика адреса TSTEP, который может влиять на счетчики даже если происходит вывод растра (тот самый конфликт при обращении к VRAM за пределами кадрового гашения). Сигнал DB_PAR сопровождает сигнал записи PWR и сигнализирует о том, что необходим проброс данных с шины CPU D[7..0] на локальную шину PAD[7..0]. Сигнал PD_RB указывает на чтение с шины PAD[7..0] внутрь PPU, и, в зависимости от источника запроса (R7 или синхрогенератор) перенаправляет данные на шину CPU D[7..0] (на это указывает сигнал XRB) или в рабочие регистры или счетчики растра PPU. Сигнал THX_MUX вырабатывается если текущий адрес PA[13..8] равен 3F (обращение к внутреннему ОЗУ палитры) и блокирует внешний сигнал записи PWR а так же сигнал XRB.
Оптимизированная схема:
http://savepic.net/10205954m.jpg
Здесь появился новый входной сигнал ALEGate, который был введен в рамках организации синхронного дизайна проекта, так как в оригинальной схеме сигнал ALE для режима синхрогенератора (чтение данных растра) организовано как OR сигнала атомарного состояния PPU H0n и инвертированного тактового сигнала PCLK. В синхронной схеме сигнал ALEGate заменяет инвертированный тактовый сигнал PCLK, делая схему строго синхронной. Verilog код модуля:
- - - Добавлено - - -Код:// Управление модулями по адресу
module PPU_ADR_CONTROL(
// Системные входы
input Clk, // Системные такты
input PClk, // Такты пикселей
input ALEGate, // Формирователь сигнала ALE
// Управляющие входы
input W7, // Запись в регистр 7
input R7, // Чтение из регистра 7
input H0n, // Слот обращения
input BLNK, // Картинка погашена
input PA8, // Адрес PA8
input PA9, // Адрес PA9
input PA10, // Адрес PA10
input PA11, // Адрес PA11
input PA12, // Адрес PA12
input PA13, // Адрес PA13
// Выходы
output PWR, // Активация записи
output TSTEP, // Счет развертки
output DB_PAR, // Данные на шину PPU
output PD_RB, // Данные в защелку
output ALE, // Сигнал ALE
output PRD, // Активация чтения
output XRB, // Данные на шину CPU
output TH_MUX // Обращение в палитру
);
// Переменные
assign TH_MUX = BLNKR & PA8 & PA9 & PA10 & PA11 & PA12 & PA13;
reg [1:0]W7Req; // Очередь запроса записи
reg [1:0]W7Queue; // Очередь сигнала записи
reg [1:0]R7Req; // Очередь запроса чтения
reg [1:0]R7Queue; // Очередь сигнала чтения
reg BLNKR; // Регистр гашения
// Выход записи
assign DB_PAR = W7Queue[1] & ~W7Queue[0];
assign PWR = TH_MUX | ~DB_PAR;
// Выход чтения
assign PD_RB = R7Queue[1] & ~R7Queue[0];
assign PRD = ~(PD_RB | (H0n & ~BLNKR));
// Выход шага счетчика растра
assign TSTEP = DB_PAR | PD_RB;
// Выход ALE
assign ALE = (~W7Queue[1] & W7Queue[0]) | (~R7Queue[1] & R7Queue[0]) | (ALEGate & ~H0n & ~BLNK);
// Данные на шину CPU
assign XRB = R7 & ~TH_MUX;
// Логика
always @(posedge Clk) begin
// Синхронизация
W7Req[0] <= W7; R7Req[0] <= R7;
// Запросы
if (W7Req[0] & ~W7) W7Req[1] <= 1'b1;
else if (W7Queue[1]) W7Req[1] <= 1'b0;
if (R7Req[0] & ~R7) R7Req[1] <= 1'b1;
else if (R7Queue[1]) R7Req[1] <= 1'b0;
// Пиксельклок
if (PClk) begin
// Ротация очереди
W7Queue[1:0] <= {W7Queue[0],W7Req[1]};
R7Queue[1:0] <= {R7Queue[0],R7Req[1]};
// Синхронизация
BLNKR <= BLNK;
end
end
// конец модуля
endmodule
Блок мультиплексирования шины данных PPU.
Это маленький блок, который работает при чтении внешним CPU внутреннего состояния PPU или его локальной шины. Схемы не было, он был сразу реализован в виде комбинаторного мультиплексора.
http://savepic.net/10210073.png
Я же его просто синхронизировал. Объяснять его работу, думаю, не требуется.
Код:// Мультиплексор шины при чтении
module PPU_BUS_MUX(
// Секция входов
input Clk, // Такты
input PClk, // Пиксельклок
input PD_RB, // Строб моста шины VRAM
input XRB, // Выбор чтения VRAM
input [7:0]PAD, // Данные VRAM
input R2, // Выбор чтения R2
input [7:0]R2DB, // Данные R2
input R4, // Выбор R4
input [7:0]OB, // Данные R4
input RPIX, // Выбор пиксельного вывода
input [7:0]PIX, // Данные пиксельного вывода
input RnW, // Выбор направления данных
input nDBE, // Строб данных
// Секция выходов
output [7:0]Q // Выход
);
// Комбинаторика
assign Q[7:0] = (RnW & ~nDBE) ? D[7:0] : 8'hZZ;
// Внутренние переменные
reg [7:0]ADR;
reg [7:0]OBR;
reg [7:0]D;
// Логика работы мультиплексора
always @(posedge Clk) begin
// Синхронизация регистра данных обьектов (спрайтов)
if (PClk) OBR[7:0] <= OB[7:0];
// Синхронизация данных чтения VRAM
if (PD_RB) ADR[7:0] <= PAD[7:0];
// Мультиплексор
if (XRB) D[7:0] <= ADR[7:0];
else if (R2) D[7:0] <= R2DB[7:0];
else if (R4) D[7:0] <= OBR[7:0];
else if (RPIX) D[7:0] <= PIX[7:0];
else D[7:0] <= 8'h00;
end
// Конец модуля
endmodule
Несколько вопросов к @HardWareMan:
1. Можно восстановить картинки? Все протухли.
2. Еще не сфоткан PAL-овский чип от Dendy 6538 (2C07)?
3. Что изменилось за 2 года?