Визуальная электроника

Для многих компьютерных энтузиастов постсоветского пространства ZX Spectrum стал первым домашним компьютером. А для некоторых и вовсе отправной точкой в программировании. Не стал исключением и автор данных строк. Заполучив в свои руки отладочную плату, способную воспроизвести speccy, было решено незамедлительно этим заняться!

 На просторах интернета можно найти руководство: "Проектирование ПК «ZX-Spectrum» на ПЛИС. Туториал для «чайников» и не только…" Воспользовавшись данным руководством, создаем ZX Spectrum 48! По пути исправляем ошибку связанную с отсутствием бордюра. Загружаем в нашу плату, и видим заветную надпись (с) 1982 Sinclair Research Ltd. Но speccy 48 это достаточно скучно. Поэтому мы добавим музыкальный сопроцессор AY-8910 и целых 128 Кб памяти!

 

Собственно начнем с добавления AY-8910. Возьмем модуль музыкального процессора из проекта Aeon-Lite. Модуль называется "ay8910.vhd". Добавляем сигналы для нового модуля в основной файл "speccy.vhd":

VHDL Code:
  1. -- AY 8912
  2. AY_D_IN : in std_logic_vector(7 downto 0);
  3. AY_D_OUT : out std_logic_vector(7 downto 0);
  4. AY_RESET : out std_logic;
  5. AY_BDIR : out std_logic;
  6. AY_CS : out std_logic;
  7. AY_BC : out std_logic

Объявим сигналы для расширения 128 К и AY-8910:

VHDL Code:
  1. ------------------------- 128K -------------------------
  2. signal port_fffd_sel : std_logic; -- AY-8910
  3. signal port_7ffd_sel : std_logic; -- 128 K
  4. signal page_ram_sel : std_logic_vector(2 downto 0); -- RAM page
  5. signal page_shadow_scr : std_logic; -- SCREEN 1/0 select
  6. signal page_rom_sel : std_logic; -- ROM 48/128 select
  7. signal page_reg_disable : std_logic; -- 1 48 KByte
  8. -- RAM bank actually being accessed
  9. signal ram_page : std_logic_vector(2 downto 0); -- RAM bus adress

Далее займемся формирование сигналов выборки музыкального сопроцессора:

VHDL Code:
  1. --AY-8912
  2. port_fffd_sel <= '0' when (cpu_a_bus(15) = '1' and cpu_a_bus(7 downto 0) = x"FD" and cpu_iorq_n = '0') else '1';
  3. AY_CS <= port_fffd_sel;
  4. AY_RESET <= nRST and res_n;
  5. AY_BDIR <= not cpu_wr_n;
  6. AY_BC <= cpu_a_bus(14);
  7. AY_D_OUT <= cpu_do_bus when (port_fffd_sel = '0') else "00000000";

Тут все достаточно просто. Сигнал выбор музыкального сопроцессора формируется при обращении к портам 0xFFFD - порт управления, и 0xBFFD - порт данных. Они отличаются сигналом шины данных процессора cpu_a_bus(14). Поэтому мы проверяем что сигнал cpu_a_bus(15) находится в единице. А младшие 8 бит шины адреса равно 0xFD. В принципе никто не запрещает проверить полностью 16 битную шину адреса. На VHDL это просто изменение одной строки кода. Но вот в реальном железе это создает 16 битный компаратор, что для 1986 года являлось непозволительной роскошью :) Сигнал cpu_a_bus(14) подаем на вход AY-BC компонента AY-8910, управляющий типом входных данных (управление/данные). Далее нам нужно как то вывести звук на внешний усилитель. Для этого нам поможет сигма/дельта цап:

Verilog Code:
  1. module dac(DACout, DACin, Clk);
  2. output DACout; // This is the average output that feeds low pass filter
  3. reg DACout; // for optimum performance, ensure that this ff is in IOB
  4. input [7:0] DACin; // DAC input
  5. input Clk;
  6. reg [9:0] DeltaAdder; // Output of Delta adder
  7. reg [9:0] SigmaAdder; // Output of Sigma adder
  8. reg [9:0] SigmaLatch; // Latches output of Sigma adder
  9. reg [9:0] DeltaB; // B input of Delta adder
  10. always @(SigmaLatch) DeltaB = {SigmaLatch[9], SigmaLatch[9]} << (8);
  11. always @(DACin or DeltaB) DeltaAdder = DACin + DeltaB;
  12. always @(DeltaAdder or SigmaLatch) SigmaAdder = DeltaAdder + SigmaLatch;
  13. always @(posedge Clk)
  14. begin
  15. SigmaLatch <= SigmaAdder;
  16. DACout <= SigmaLatch[9];
  17. end
  18. endmodule

Некоторые demo требуют еще и чтение регистров AY-8910. Не проблема, добавляем чтение:

VHDL Code:
  1. cpu_di_bus <= rom_do when (rom_sel = '1' and cpu_mreq_n = '0') else
  2. ram_data when (rom_sel = '0' and cpu_mreq_n = '0') else
  3. "1" & TAPE_IN & "1" & kb_do_bus when (port_fe_sel = '0') else
  4. AY_D_IN when (port_fffd_sel='0') else "11111111";

Теперь наш спектрум обзавелся стерео звуком! Можно загружать демки! Но мы пойдем дальше, добавим 128 Кбайт памяти:

VHDL Code:
  1. port_7ffd_sel <= '0' when (cpu_a_bus(15) = '0' and cpu_a_bus(7 downto 0) = x"FD" and cpu_iorq_n = '0') else '1';
  2.  
  3. ram_page <= page_ram_sel when cpu_a_bus(15 downto 14) = "11" else -- Selectable bank at 0xc000
  4. cpu_a_bus(14) & cpu_a_bus(15 downto 14); -- A=bank: 00=XXX, 01=101, 10=010, 11=XXX
  5.  
  6. -- 128K paging register
  7. process(clock,reset_n)
  8. begin
  9. if reset_n = '0' then
  10. page_reg_disable <= '0';
  11. page_rom_sel <= '0';
  12. page_shadow_scr <= '0';
  13. page_ram_sel <= (others => '0');
  14. elsif rising_edge(clock) then
  15. if port_7ffd_sel = '0' and page_reg_disable = '0' and cpu_wr_n = '0' then
  16. page_reg_disable <= cpu_do_bus(5);
  17. page_rom_sel <= cpu_do_bus(4);
  18. page_shadow_scr <= cpu_do_bus(3);
  19. page_ram_sel <= cpu_do_bus(2 downto 0);
  20. end if;
  21. end if;
  22. end process;

Тут в принципе все аналогично AY-8910. Управление дополнительными страницами памяти осуществляется с помощью порта 0x7FFD. В этом коде мы его и реализовали. Чтоб лучше понять внесенные изменения предлагаю взглянуть на схему:

Осталось поменять управление видео и основной памятью:

VHDL Code:
  1. process (vid_sel, vcnt, hcnt, cpu_a_bus)
  2. begin
  3. if vid_sel = '1' then
  4.  
  5. if page_shadow_scr = '1' then --Video from bank 7
  6. case hcnt(0) is
  7. --when '0' => ram_adr <= "000010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3);
  8. --when '1' => ram_adr <= "000010110" & vcnt(8 downto 4) & hcnt(7 downto 3);
  9. when '0' => ram_adr <= "001110" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3);
  10. when '1' => ram_adr <= "001110110" & vcnt(8 downto 4) & hcnt(7 downto 3);
  11. end case;
  12. end if;
  13.  
  14. if page_shadow_scr = '0' then --Video from bank 5
  15. case hcnt(0) is
  16. --when '0' => ram_adr <= "000010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3);
  17. --when '1' => ram_adr <= "000010110" & vcnt(8 downto 4) & hcnt(7 downto 3);
  18. when '0' => ram_adr <= "001010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3);
  19. when '1' => ram_adr <= "001010110" & vcnt(8 downto 4) & hcnt(7 downto 3);
  20. end case;
  21. end if;
  22.  
  23. else
  24. --ram_adr <= "000" & cpu_a_bus;
  25. ram_adr <= "00" & ram_page & cpu_a_bus(13 downto 0);
  26. end if;
  27. end process;

Также необходимо изменить компонент lpm_rom, изменив его размер до 32 Кбайт. Загрузив в него объединенное ПЗУ Spectrum 48K/128K. И добавив сигнал выборки пзу:

VHDL Code:
  1. ROM: lpm_rom0
  2. port map(
  3. address => page_rom_sel & cpu_a_bus(13 downto 0),
  4. clock => clock,
  5. q => rom_do
  6. );

Схема того что у нас получилось:

На радостях загружаем какое-нибудь супер мега демо!

Проект: zx_spectrum

Добавить комментарий