Для многих компьютерных энтузиастов постсоветского пространства 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:
-- AY 8912 AY_D_IN : in std_logic_vector(7 downto 0); AY_D_OUT : out std_logic_vector(7 downto 0); AY_RESET : out std_logic; AY_BDIR : out std_logic; AY_CS : out std_logic; AY_BC : out std_logic
Объявим сигналы для расширения 128 К и AY-8910:
VHDL Code:
------------------------- 128K ------------------------- signal port_fffd_sel : std_logic; -- AY-8910 signal port_7ffd_sel : std_logic; -- 128 K signal page_ram_sel : std_logic_vector(2 downto 0); -- RAM page signal page_shadow_scr : std_logic; -- SCREEN 1/0 select signal page_rom_sel : std_logic; -- ROM 48/128 select signal page_reg_disable : std_logic; -- 1 48 KByte -- RAM bank actually being accessed signal ram_page : std_logic_vector(2 downto 0); -- RAM bus adress
Далее займемся формирование сигналов выборки музыкального сопроцессора:
VHDL Code:
--AY-8912 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'; AY_CS <= port_fffd_sel; AY_RESET <= nRST and res_n; AY_BDIR <= not cpu_wr_n; AY_BC <= cpu_a_bus(14); 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:
module dac(DACout, DACin, Clk); output DACout; // This is the average output that feeds low pass filter reg DACout; // for optimum performance, ensure that this ff is in IOB input [7:0] DACin; // DAC input input Clk; reg [9:0] DeltaAdder; // Output of Delta adder reg [9:0] SigmaAdder; // Output of Sigma adder reg [9:0] SigmaLatch; // Latches output of Sigma adder reg [9:0] DeltaB; // B input of Delta adder always @(SigmaLatch) DeltaB = {SigmaLatch[9], SigmaLatch[9]} << (8); always @(DACin or DeltaB) DeltaAdder = DACin + DeltaB; always @(DeltaAdder or SigmaLatch) SigmaAdder = DeltaAdder + SigmaLatch; always @(posedge Clk) begin SigmaLatch <= SigmaAdder; DACout <= SigmaLatch[9]; end endmodule
Некоторые demo требуют еще и чтение регистров AY-8910. Не проблема, добавляем чтение:
VHDL Code:
cpu_di_bus <= rom_do when (rom_sel = '1' and cpu_mreq_n = '0') else ram_data when (rom_sel = '0' and cpu_mreq_n = '0') else "1" & TAPE_IN & "1" & kb_do_bus when (port_fe_sel = '0') else AY_D_IN when (port_fffd_sel='0') else "11111111";
Теперь наш спектрум обзавелся стерео звуком! Можно загружать демки! Но мы пойдем дальше, добавим 128 Кбайт памяти:
VHDL Code:
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'; ram_page <= page_ram_sel when cpu_a_bus(15 downto 14) = "11" else -- Selectable bank at 0xc000 cpu_a_bus(14) & cpu_a_bus(15 downto 14); -- A=bank: 00=XXX, 01=101, 10=010, 11=XXX -- 128K paging register process(clock,reset_n) begin if reset_n = '0' then page_reg_disable <= '0'; page_rom_sel <= '0'; page_shadow_scr <= '0'; page_ram_sel <= (others => '0'); elsif rising_edge(clock) then if port_7ffd_sel = '0' and page_reg_disable = '0' and cpu_wr_n = '0' then page_reg_disable <= cpu_do_bus(5); page_rom_sel <= cpu_do_bus(4); page_shadow_scr <= cpu_do_bus(3); page_ram_sel <= cpu_do_bus(2 downto 0); end if; end if; end process;
Тут в принципе все аналогично AY-8910. Управление дополнительными страницами памяти осуществляется с помощью порта 0x7FFD. В этом коде мы его и реализовали. Чтоб лучше понять внесенные изменения предлагаю взглянуть на схему:
Осталось поменять управление видео и основной памятью:
VHDL Code:
process (vid_sel, vcnt, hcnt, cpu_a_bus) begin if vid_sel = '1' then if page_shadow_scr = '1' then --Video from bank 7 case hcnt(0) is --when '0' => ram_adr <= "000010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3); --when '1' => ram_adr <= "000010110" & vcnt(8 downto 4) & hcnt(7 downto 3); when '0' => ram_adr <= "001110" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3); when '1' => ram_adr <= "001110110" & vcnt(8 downto 4) & hcnt(7 downto 3); end case; end if; if page_shadow_scr = '0' then --Video from bank 5 case hcnt(0) is --when '0' => ram_adr <= "000010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3); --when '1' => ram_adr <= "000010110" & vcnt(8 downto 4) & hcnt(7 downto 3); when '0' => ram_adr <= "001010" & vcnt(8 downto 7) & vcnt(3 downto 1) & vcnt(6 downto 4) & hcnt(7 downto 3); when '1' => ram_adr <= "001010110" & vcnt(8 downto 4) & hcnt(7 downto 3); end case; end if; else --ram_adr <= "000" & cpu_a_bus; ram_adr <= "00" & ram_page & cpu_a_bus(13 downto 0); end if; end process;
Также необходимо изменить компонент lpm_rom, изменив его размер до 32 Кбайт. Загрузив в него объединенное ПЗУ Spectrum 48K/128K. И добавив сигнал выборки пзу:
VHDL Code:
ROM: lpm_rom0 port map( address => page_rom_sel & cpu_a_bus(13 downto 0), clock => clock, q => rom_do );
Схема того что у нас получилось:
На радостях загружаем какое-нибудь супер мега демо!
Проект: zx_spectrum