Files
SharpMZ_MiSTer/rtl/mz80c/mz80c_video.vhd
birdybro b32c9615e4 Restructure folders
Proposal to restructure all the files and folders to keep things organized and similar to other cores.
2022-06-24 14:04:27 -06:00

1460 lines
78 KiB
VHDL

---------------------------------------------------------------------------------------------------------
--
-- Name: mz80c_video.vhd
-- Created: July 2018
-- Author(s): Philip Smart
-- Description: Sharp MZ series Video logic.
-- This module fully emulates the Sharp MZ Personal Computer series video display
-- logic plus extensions.
-- The display is capable of performing 40x25, 80x25 Mono/Colour display along with
-- a Programmable Character Generator and a bit mapped 320x200/640x200 framebuffer.
-- The design is slightly different to the original Sharps in that I use a dual
-- buffer technique, ie. the original 1K/2K VRAM + ARAM and a pixel mapped displaybuffer.
-- During Vertical blanking, the VRAM+ARAM is copied and expanded into the display
-- buffer which is then displayed during the next display window. Part of the reasoning
-- was to cut down on snow/tearing on the older K/C models (but still provide the
-- blanking signals so any original software works) and also allow the option of
-- disabling the MZ80A/700 wait states.
-- As an addition, I added a graphics framebuffer (320x200, 640x200 8 colours)
-- the interface to which is, at the moment, non-standard, but as I get more details
-- on add on cards, I can add mapping layers so this graphics framebuffer can be used
-- by customised software. Pixels drawn in the graphics framebuffer can be blended into
-- the main display buffer via programmable logic mode (ie. XOR, OR etc).
-- A lot of timing information can be found in the docs/SharpMZ_Notes.xlsx spreadsheet,
-- but the main info is:
-- MZ80K/C/1200/A (Monochrome)
-- Signal Start End Period Comment
-- 64uS 15.625KHz
-- HDISPEN 0 320 40uS
-- HBLANK 318 510 24uS
-- BLNK 318 486 21uS
-- HSYNC 393 438 5.625uS
--
-- 16.64mS 60.10Hz
-- VDISPEN 0 200 12.8mS
-- VSYNC 219 223 256uS
-- VBLANK 201 259 3.712mS not VDISPEN
--
-- MZ700 (Colour)
-- Signal Start End Period Comment
-- 64.056uS 15.611KHz
-- HDISPEN 0 320 36.088uS
-- HBLANK 320 567 27.968uS
-- BLNK 320 548 25.7126uS
-- HSYNC 400 440 4.567375uS
--
-- 16.654mS 50.0374Hz
-- VDISPEN 0 200 12.8112mS
-- VSYNC 212 215 0.19216ms
-- VBLANK 201 311 7.1738mS not VDISPEN
--
-- Copyright: (c) 2018 Philip Smart <philip.smart@net2net.org>
--
-- History: July 2018 - Initial module written.
-- August 2018 - Main portions written, including the display buffer.
-- September 2018 - Added the graphics framebuffer.
--
---------------------------------------------------------------------------------------------------------
-- This source file is free software: you can redistribute it and-or modify
-- it under the terms of the GNU General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This source file is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http:--www.gnu.org-licenses->.
---------------------------------------------------------------------------------------------------------
library ieee;
library pkgs;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
use pkgs.clkgen_pkg.all;
use pkgs.mctrl_pkg.all;
entity mz80c_video is
Port (
RST_n : in std_logic; -- Reset
-- Different operations modes.
CONFIG : in std_logic_vector(CONFIG_WIDTH);
-- Clocks
CLKBUS : in std_logic_vector(CLKBUS_WIDTH); -- Clock signals created by clkgen module.
-- CPU Signals
T80_A : in std_logic_vector(13 downto 0); -- CPU Address Bus
T80_RD_n : in std_logic; -- CPU Read Signal
T80_WR_n : in std_logic; -- CPU Write Signal
T80_MREQ_n : in std_logic; -- CPU Memory Request
T80_BUSACK_n : in std_logic; -- CPU Bus Acknowledge
T80_WAIT_n : out std_logic; -- CPU Wait Request
T80_DI : in std_logic_vector(7 downto 0); -- CPU Data Bus in
T80_DO : out std_logic_vector(7 downto 0); -- CPU Data Bus out
-- Selects.
CS_D_n : in std_logic; -- VRAM Select
CS_E_n : in std_logic; -- Peripherals Select
CS_G_n : in std_logic; -- GRAM Select
CS_IO_GRAM_n : in std_logic; -- GRAM IO Select range E8 - EF
-- Video Signals
VGATE_n : in std_logic; -- Video Output Control
HBLANK : out std_logic; -- Horizontal Blanking
VBLANK : out std_logic; -- Vertical Blanking
HSYNC_n : out std_logic; -- Horizontal Sync
VSYNC_n : out std_logic; -- Vertical Sync
ROUT : out std_logic; -- Red Output
GOUT : out std_logic; -- Green Output
BOUT : out std_logic; -- Green Output
-- HPS Interface
IOCTL_DOWNLOAD : in std_logic; -- HPS Downloading to FPGA.
IOCTL_UPLOAD : in std_logic; -- HPS Uploading from FPGA.
IOCTL_CLK : in std_logic; -- HPS I/O Clock..
IOCTL_WR : in std_logic; -- HPS Write Enable to FPGA.
IOCTL_RD : in std_logic; -- HPS Read Enable from FPGA.
IOCTL_ADDR : in std_logic_vector(24 downto 0); -- HPS Address in FPGA to write into.
IOCTL_DOUT : in std_logic_vector(15 downto 0); -- HPS Data to be written into FPGA.
IOCTL_DIN : out std_logic_vector(15 downto 0) -- HPS Data to be read into HPS.
);
end mz80c_video;
architecture RTL of mz80c_video is
--
-- Registers
--
signal MAX_COLUMN : integer range 0 to 80;
signal MAX_ROW : integer range 0 to 25;
signal MAX_SUBROW : integer range 0 to 8;
signal FB_ADDR : std_logic_vector(13 downto 0); -- Frame buffer actual address
signal OFFSET_ADDR : std_logic_vector(7 downto 0); -- Display Offset - for MZ1200/80A machines with 2K VRAM
signal SR_G_DATA : std_logic_vector(7 downto 0); -- Shift Register to Display Green
signal SR_R_DATA : std_logic_vector(7 downto 0); -- Shift Register to Display Red
signal SR_B_DATA : std_logic_vector(7 downto 0); -- Shift Register to Display Blue
signal DISPLAY_DATA : std_logic_vector(23 downto 0);
signal XFER_ADDR : std_logic_vector(10 downto 0);
signal XFER_SUB_ADDR : std_logic_vector(2 downto 0);
signal XFER_VRAM_DATA : std_logic_vector(15 downto 0);
signal XFER_GRAM_DATA : std_logic_vector(23 downto 0);
signal XFER_MAPPED_DATA : std_logic_vector(23 downto 0);
signal XFER_WEN : std_logic;
signal XFER_VRAM_ADDR : std_logic_vector(10 downto 0);
signal XFER_DST_ADDR : std_logic_vector(13 downto 0);
signal XFER_CGROM_ADDR : std_logic_vector(11 downto 0);
signal CGROM_DATA : std_logic_vector(7 downto 0); -- Font Data To Display
signal DISPLAY_INVERT : std_logic; -- Invert display Mode of MZ80A/1200
--
-- CPU/Video Access
--
signal VRAM_VIDEO_DATA : std_logic_vector(7 downto 0); -- Display data output to CPU.
signal VRAM_ADDR : std_logic_vector(11 downto 0); -- VRAM Address.
signal VRAM_DATA_IN : std_logic_vector(7 downto 0); -- VRAM Data in.
signal VRAM_DATA_OUT : std_logic_vector(7 downto 0); -- VRAM Data out.
signal VRAM_WEN : std_logic; -- VRAM Write enable signal.
signal VRAM_CLK : std_logic; -- Clock used to access the VRAM (CKMEM or IOCTL_CLK).
signal GRAM_VIDEO_DATA : std_logic_vector(7 downto 0); -- Graphics display data output to CPU.
signal GRAM_ADDR : std_logic_vector(13 downto 0); -- Graphics RAM Address.
signal GRAM_DATA_IN_R : std_logic_vector(7 downto 0); -- Graphics Red RAM Data.
signal GRAM_DATA_IN_G : std_logic_vector(7 downto 0); -- Graphics Green RAM Data.
signal GRAM_DATA_IN_B : std_logic_vector(7 downto 0); -- Graphics Blue RAM Data.
signal GRAM_DATA_OUT_R : std_logic_vector(7 downto 0); -- Graphics Red RAM Data out.
signal GRAM_DATA_OUT_G : std_logic_vector(7 downto 0); -- Graphics Green RAM Data out.
signal GRAM_DATA_OUT_B : std_logic_vector(7 downto 0); -- Graphics Blue RAM Data out.
signal GRAM_WEN_R : std_logic; -- Graphics Red RAM Write enable signal.
signal GRAM_WEN_G : std_logic; -- Graphics Green RAM Write enable signal.
signal GRAM_WEN_B : std_logic; -- Graphics Blue RAM Write enable signal.
signal GRAM_CLK : std_logic; -- Clock used to access the GRAM (CKMEM or IOCTL_CLK).
signal GRAM_MODE : std_logic_vector(7 downto 0); -- Programmable mode register to control GRAM operations.
signal GRAM_RED_WRITER : std_logic_vector(7 downto 0); -- Red pixel writer filter.
signal GRAM_GREEN_WRITER : std_logic_vector(7 downto 0); -- Green pixel writer filter.
signal GRAM_BLUE_WRITER : std_logic_vector(7 downto 0); -- Blue pixel writer filter.
signal T80_MA : std_logic_vector(11 downto 0); -- CPU Address Masked according to machine model.
signal CS_INVERT_n : std_logic; -- Chip Select to enable Inverse mode.
signal CS_SCROLL_n : std_logic; -- Chip Select to perform a hardware scroll.
signal CS_IO_EA_n : std_logic; -- Chip Select to write to the Graphics mode register.
signal CS_IO_EB_n : std_logic; -- Chip Select to write to the Red pixel per byte indirect write register.
signal CS_IO_EC_n : std_logic; -- Chip Select to write to the Green pixel per byte indirect write register.
signal CS_IO_ED_n : std_logic; -- Chip Select to write to the Blue pixel per byte indirect write register.
signal CS_PCG_n : std_logic;
signal WAITi_n : std_logic; -- Wait
signal WAITii_n : std_logic; -- Wait(delayed)
signal VWEN : std_logic; -- Write enable to VRAM.
signal GWEN_R : std_logic; -- Write enable to Red GRAM.
signal GWEN_G : std_logic; -- Write enable to Green GRAM.
signal GWEN_B : std_logic; -- Write enable to Blue GRAM.
--
-- Internal Signals
--
signal H_COUNT : unsigned(10 downto 0); -- Horizontal pixel counter
signal H_BLANKi : std_logic; -- Horizontal Blanking
signal H_SYNC_ni : std_logic; -- Horizontal Blanking
signal H_DISPLAY_START : integer range 0 to 2047;
signal H_DISPLAY_END : integer range 0 to 2047;
signal H_BLNK_START : integer range 0 to 2047;
signal H_BLNK_END : integer range 0 to 2047;
signal H_SYNC_START : integer range 0 to 2047;
signal H_SYNC_END : integer range 0 to 2047;
signal H_LINE_END : integer range 0 to 2047;
signal V_COUNT : unsigned(10 downto 0); -- Vertical pixel counter
signal V_BLANKi : std_logic; -- Vertical Blanking
signal V_SYNC_ni : std_logic; -- Horizontal Blanking
signal V_DISPLAY_START : integer range 0 to 2047;
signal V_DISPLAY_END : integer range 0 to 2047;
signal V_SYNC_START : integer range 0 to 2047;
signal V_SYNC_END : integer range 0 to 2047;
signal V_LINE_END : integer range 0 to 2047;
signal BLNK : std_logic; -- Horizontal Blanking CPU Wait interval
signal BLNK_MEMACCESS : std_logic; -- Horizontal Blanking Memory Access
--
-- CG-ROM
--
signal CGROM_DO : std_logic_vector(7 downto 0);
signal CGROM_BANK : std_logic_vector(3 downto 0);
--
-- PCG
--
signal CGRAM_DO : std_logic_vector(7 downto 0);
signal CG_ADDR : std_logic_vector(11 downto 0);
signal CGRAM_ADDR : std_logic_vector(11 downto 0);
signal PCG_DATA : std_logic_vector(7 downto 0);
signal CGRAM_DI : std_logic_vector(7 downto 0);
signal CGRAM_WE_n : std_logic;
signal CGRAM_WEN : std_logic;
signal CGRAM_SEL : std_logic;
--
-- HPS Control.
--
signal IOCTL_CS_VRAM_n : std_logic; -- Chip Select to allow the HPS to access the VRAM.
signal IOCTL_CS_GRAM_n : std_logic; -- Chip Select to allow the HPS to access the GRAM.
signal IOCTL_WEN_VRAM : std_logic; -- Write Enable to allow the HPS to write to VRAM.
signal IOCTL_WEN_GRAM_R : std_logic; -- Write Enable to allow the HPS to write to the Red GRAM.
signal IOCTL_WEN_GRAM_G : std_logic; -- Write Enable to allow the HPS to write to the Green GRAM.
signal IOCTL_WEN_GRAM_B : std_logic; -- Write Enable to allow the HPS to write to the Blue GRAM.
signal IOCTL_DIN_VRAM : std_logic_vector(7 downto 0);
signal IOCTL_DIN_GRAM : std_logic_vector(7 downto 0);
signal IOCTL_DIN_PCG : std_logic_vector(15 downto 0);
signal IOCTL_CS_CGROM_n : std_logic;
signal IOCTL_CS_CGRAM_n : std_logic;
signal IOCTL_WEN_CGROM : std_logic;
signal IOCTL_WEN_CGRAM : std_logic;
signal IOCTL_DIN_CGROM : std_logic_vector(7 downto 0);
signal IOCTL_DIN_CGRAM : std_logic_vector(7 downto 0);
--
-- Components
--
component dpram
generic (
init_file : string;
widthad_a : natural;
width_a : natural;
widthad_b : natural;
width_b : natural;
outdata_reg_a : string := "UNREGISTERED";
outdata_reg_b : string := "UNREGISTERED"
);
Port (
clock_a : in std_logic ;
clocken_a : in std_logic := '1';
address_a : in std_logic_vector (widthad_a-1 downto 0);
data_a : in std_logic_vector (width_a-1 downto 0);
wren_a : in std_logic;
q_a : out std_logic_vector (width_a-1 downto 0);
clock_b : in std_logic ;
clocken_b : in std_logic := '1';
address_b : in std_logic_vector (widthad_b-1 downto 0);
data_b : in std_logic_vector (width_b-1 downto 0);
wren_b : in std_logic;
q_b : out std_logic_vector (width_b-1 downto 0)
);
end component;
begin
--
-- Instantiation
--
-- Video memory as seen by the MZ Series. This is a 1K or 2K or 2K + 2K Attribute RAM
-- organised as 4K x 8 on the CPU side and 2K x 16 on the display side, top bits are not used for MZ80K/C/1200/A.
--
VRAM0 : dpram
GENERIC MAP (
init_file => null,
widthad_a => 12,
width_a => 8,
widthad_b => 11,
width_b => 16,
outdata_reg_b => "UNREGISTERED"
)
PORT MAP (
-- Port A used for CPU access.
clock_a => VRAM_CLK,
clocken_a => '1',
address_a => VRAM_ADDR,
data_a => VRAM_DATA_IN,
wren_a => VRAM_WEN,
q_a => VRAM_DATA_OUT,
-- Port B used for VRAM -> DISPLAY BUFFER transfer (SOURCE).
clock_b => CLKBUS(CKSYS),
clocken_b => '1',
address_b => XFER_VRAM_ADDR,
data_b => (others => '0'),
wren_b => '0',
q_b => XFER_VRAM_DATA
);
-- Graphics frame buffer memory. This is an enhancement and allows for 320x200 or 640x200 pixel display in 8 colours. It matches
-- the output frame buffer in size, so the contents are blended by a programmable logical operator (ie. OR) with the expanded Video
-- Ram contents to create the output display.
--
GRAMG : dpram -- GREEN
GENERIC MAP (
init_file => null,
widthad_a => 14,
width_a => 8,
widthad_b => 14,
width_b => 8,
outdata_reg_b => "UNREGISTERED"
)
PORT MAP (
-- Port A used for CPU access.
clock_a => GRAM_CLK,
clocken_a => '1',
address_a => GRAM_ADDR,
data_a => GRAM_DATA_IN_G,
wren_a => GRAM_WEN_G,
q_a => GRAM_DATA_OUT_G,
-- Port B used for VRAM -> DISPLAY BUFFER transfer (SOURCE).
clock_b => CLKBUS(CKSYS),
clocken_b => '1',
address_b => XFER_DST_ADDR, -- FB Destination address is used as GRAM is on a 1:1 mapping with FB.
data_b => (others => '0'),
wren_b => '0',
q_b => XFER_GRAM_DATA(7 downto 0)
);
--
GRAMR : dpram -- RED
GENERIC MAP (
init_file => null,
widthad_a => 14,
width_a => 8,
widthad_b => 14,
width_b => 8,
outdata_reg_b => "UNREGISTERED"
)
PORT MAP (
-- Port A used for CPU access.
clock_a => GRAM_CLK,
clocken_a => '1',
address_a => GRAM_ADDR,
data_a => GRAM_DATA_IN_R,
wren_a => GRAM_WEN_R,
q_a => GRAM_DATA_OUT_R,
-- Port B used for VRAM -> DISPLAY BUFFER transfer (SOURCE).
clock_b => CLKBUS(CKSYS),
clocken_b => '1',
address_b => XFER_DST_ADDR, -- FB Destination address is used as GRAM is on a 1:1 mapping with FB.
data_b => (others => '0'),
wren_b => '0',
q_b => XFER_GRAM_DATA(15 downto 8)
);
--
GRAMB : dpram -- BLUE
GENERIC MAP (
init_file => null,
widthad_a => 14,
width_a => 8,
widthad_b => 14,
width_b => 8,
outdata_reg_b => "UNREGISTERED"
)
PORT MAP (
-- Port A used for CPU access.
clock_a => GRAM_CLK,
clocken_a => '1',
address_a => GRAM_ADDR,
data_a => GRAM_DATA_IN_B,
wren_a => GRAM_WEN_B,
q_a => GRAM_DATA_OUT_B,
-- Port B used for VRAM -> DISPLAY BUFFER transfer (SOURCE).
clock_b => CLKBUS(CKSYS),
clocken_b => '1',
address_b => XFER_DST_ADDR, -- FB Destination address is used as GRAM is on a 1:1 mapping with FB.
data_b => (others => '0'),
wren_b => '0',
q_b => XFER_GRAM_DATA(23 downto 16)
);
-- Display Buffer Memory, organised in a Row x Col format, where Address = (Row * MAX_COLUMN * 8) + Col,
-- but in real terms it is a 320x200x3 or 640x200x3 frame buffer.
--
FRAMEBUF0 : dpram
GENERIC MAP (
init_file => null,
widthad_a => 14,
width_a => 24,
widthad_b => 14,
width_b => 24,
outdata_reg_b => "UNREGISTERED"
)
PORT MAP (
-- Port A used for Display output.
clock_a => CLKBUS(CKVIDEO),
clocken_a => '1',
address_a => FB_ADDR,
data_a => (others => '0'),
wren_a => '0',
q_a => DISPLAY_DATA,
-- Port B used for VRAM -> DISPLAY BUFFER transfer (DESTINATION).
clock_b => CLKBUS(CKSYS),
clocken_b => '1',
address_b => XFER_DST_ADDR,
data_b => XFER_MAPPED_DATA,
wren_b => XFER_WEN
--q_b =>
);
-- 0 = MZ80K CGROM = 2Kbytes -> 0000:07ff
-- 1 = MZ80C CGROM = 2Kbytes -> 0800:0fff
-- 2 = MZ1200 CGROM = 2Kbytes -> 1000:17ff
-- 3 = MZ80A CGROM = 2Kbytes -> 1800:1fff
-- 4 = MZ700 CGROM = 4Kbytes -> 2000:2fff
--
CGROM0 : dpram
GENERIC MAP (
init_file => "./mif/combined_cgrom.mif",
widthad_a => 15,
width_a => 8,
widthad_b => 15,
width_b => 8
)
PORT MAP (
clock_a => CLKBUS(CKSYS),
clocken_a => '1',
address_a => CGROM_BANK & CG_ADDR(10 downto 0),
data_a => (others => '0'),
wren_a => '0',
q_a => CGROM_DO,
clock_b => IOCTL_CLK,
clocken_b => '1',
address_b => IOCTL_ADDR(14 downto 0),
data_b => IOCTL_DOUT(7 DOWNTO 0),
wren_b => IOCTL_WEN_CGROM,
q_b => IOCTL_DIN_CGROM
);
CGRAM : dpram
GENERIC MAP (
init_file => "./mif/combined_cgrom.mif",
widthad_a => 15,
width_a => 8,
widthad_b => 15,
width_b => 8
)
PORT MAP (
clock_a => CLKBUS(CKSYS),
clocken_a => '1',
address_a => CGROM_BANK & CG_ADDR(10 downto 0),
data_a => CGRAM_DI,
wren_a => CGRAM_WEN,
q_a => CGRAM_DO,
clock_b => IOCTL_CLK,
clocken_b => '1',
address_b => IOCTL_ADDR(14 DOWNTO 0),
data_b => IOCTL_DOUT(7 DOWNTO 0),
wren_b => IOCTL_WEN_CGRAM,
q_b => IOCTL_DIN_CGRAM
);
-- Clock as maximum system speed to minimise transfer time.
--
process( RST_n, CLKBUS(CKSYS) )
variable XFER_CYCLE : integer range 0 to 6;
variable XFER_SRC_COL : integer range 0 to 80;
variable XFER_SRC_ROW : integer range 0 to 25;
variable XFER_DST_COL : integer range 0 to 80;
variable XFER_DST_ROW : integer range 0 to 25;
variable XFER_DST_SUBROW : integer range 0 to 7;
variable MAPPED_DATA : std_logic_vector(23 downto 0);
begin
if RST_n='0' then
XFER_VRAM_ADDR <= (others => '0');
XFER_DST_ADDR <= (others => '0');
XFER_CGROM_ADDR <= (others => '0');
XFER_SRC_COL := 0;
XFER_SRC_ROW := 0;
XFER_DST_COL := 0;
XFER_DST_SUBROW := 0;
XFER_DST_ROW := 0;
XFER_CYCLE := 0;
XFER_WEN <= '0';
-- Process on negative edge as the RAM locks a write on positive edge.
--
elsif CLKBUS(CKSYS)'event and CLKBUS(CKSYS)='0' then
-- If we are in the active transfer window, start transfer.
if V_COUNT >= V_DISPLAY_END and XFER_DST_ROW < MAX_ROW then
-- Finite state machine to implement read, map and write.
case (XFER_CYCLE) is
-- Setup the source character address.
when 0 =>
if CONFIG(MZ_KC) = '1' then
XFER_VRAM_ADDR <= std_logic_vector(to_unsigned((XFER_SRC_ROW * MAX_COLUMN) + XFER_SRC_COL, 11));
else
XFER_VRAM_ADDR <= std_logic_vector(to_unsigned((XFER_SRC_ROW * MAX_COLUMN) + XFER_SRC_COL, 11)) + (OFFSET_ADDR & "000");
end if;
XFER_CYCLE := 1;
-- Get the source character and map via the PCG to a slice of the displayed character.
-- Recalculate the destination address based on this loops values.
when 1 =>
-- Setup the PCG address based on the read character.
XFER_CGROM_ADDR <= XFER_VRAM_DATA(15) & XFER_VRAM_DATA(7 downto 0) & std_logic_vector(to_unsigned(XFER_DST_SUBROW, 3));
-- Destination is recalculated each loop due to subrow changing.
-- As the Graphics framebuffer is on a 1-1, we use the same address counter to read out data from GRAM.
XFER_DST_ADDR <= std_logic_vector(to_unsigned((((XFER_DST_ROW * MAX_SUBROW) + XFER_DST_SUBROW) * MAX_COLUMN) + XFER_DST_COL, 14));
XFER_CYCLE := 2;
-- An extra clock needed for the CGROM to settle.
when 2 =>
XFER_CYCLE := 3;
-- Expand and store the slice of the character.
when 3 =>
-- Graphics mode:- 7/6 = Operator (00=OR,01=AND,10=NAND,11=XOR),
-- 5 = GRAM Output Enable 0 = active.
-- 4 = VRAM Output Enable, 0 = active.
-- 3/2 = Write mode (00=Page 1:Red, 01=Page 2:Green, 10=Page 3:Blue, 11=Indirect),
-- 1/0 = Read mode (00=Page 1:Red, 01=Page2:Green, 10=Page 3:Blue, 11=Not used).
if CONFIG(VRAMDISABLE) = '0' and GRAM_MODE(4) = '0' then
if CONFIG(COLOUR) = '1' or CONFIG(COLOUR80) = '1' then
if CGROM_DATA(7) = '0' then
MAPPED_DATA(7) := XFER_VRAM_DATA(10);
MAPPED_DATA(15) := XFER_VRAM_DATA(9);
MAPPED_DATA(23) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(7) := XFER_VRAM_DATA(14);
MAPPED_DATA(15) := XFER_VRAM_DATA(13);
MAPPED_DATA(23) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(6) = '0' then
MAPPED_DATA(6) := XFER_VRAM_DATA(10);
MAPPED_DATA(14) := XFER_VRAM_DATA(9);
MAPPED_DATA(22) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(6) := XFER_VRAM_DATA(14);
MAPPED_DATA(14) := XFER_VRAM_DATA(13);
MAPPED_DATA(22) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(5) = '0' then
MAPPED_DATA(5) := XFER_VRAM_DATA(10);
MAPPED_DATA(13) := XFER_VRAM_DATA(9);
MAPPED_DATA(21) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(5) := XFER_VRAM_DATA(14);
MAPPED_DATA(13) := XFER_VRAM_DATA(13);
MAPPED_DATA(21) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(4) = '0' then
MAPPED_DATA(4) := XFER_VRAM_DATA(10);
MAPPED_DATA(12) := XFER_VRAM_DATA(9);
MAPPED_DATA(20) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(4) := XFER_VRAM_DATA(14);
MAPPED_DATA(12) := XFER_VRAM_DATA(13);
MAPPED_DATA(20) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(3) = '0' then
MAPPED_DATA(3) := XFER_VRAM_DATA(10);
MAPPED_DATA(11) := XFER_VRAM_DATA(9);
MAPPED_DATA(19) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(3) := XFER_VRAM_DATA(14);
MAPPED_DATA(11) := XFER_VRAM_DATA(13);
MAPPED_DATA(19) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(2) = '0' then
MAPPED_DATA(2) := XFER_VRAM_DATA(10);
MAPPED_DATA(10) := XFER_VRAM_DATA(9);
MAPPED_DATA(18) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(2) := XFER_VRAM_DATA(14);
MAPPED_DATA(10) := XFER_VRAM_DATA(13);
MAPPED_DATA(18) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(1) = '0' then
MAPPED_DATA(1) := XFER_VRAM_DATA(10);
MAPPED_DATA(9) := XFER_VRAM_DATA(9);
MAPPED_DATA(17) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(1) := XFER_VRAM_DATA(14);
MAPPED_DATA(9) := XFER_VRAM_DATA(13);
MAPPED_DATA(17) := XFER_VRAM_DATA(12);
end if;
if CGROM_DATA(0) = '0' then
MAPPED_DATA(0) := XFER_VRAM_DATA(10);
MAPPED_DATA(8) := XFER_VRAM_DATA(9);
MAPPED_DATA(16) := XFER_VRAM_DATA(8);
else
MAPPED_DATA(0) := XFER_VRAM_DATA(14);
MAPPED_DATA(8) := XFER_VRAM_DATA(13);
MAPPED_DATA(16) := XFER_VRAM_DATA(12);
end if;
end if;
if CONFIG(NORMAL) = '1' or CONFIG(NORMAL80) = '1' then
if CGROM_DATA(7) = '0' then
MAPPED_DATA(7) := '0';
MAPPED_DATA(15) := '0';
MAPPED_DATA(23) := '0';
else
MAPPED_DATA(7) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(15) := '1';
MAPPED_DATA(23) := '1';
end if;
end if;
if CGROM_DATA(6) = '0' then
MAPPED_DATA(6) := '0';
MAPPED_DATA(14) := '0';
MAPPED_DATA(22) := '0';
else
MAPPED_DATA(6) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(14) := '1';
MAPPED_DATA(22) := '1';
end if;
end if;
if CGROM_DATA(5) = '0' then
MAPPED_DATA(5) := '0';
MAPPED_DATA(13) := '0';
MAPPED_DATA(21) := '0';
else
MAPPED_DATA(5) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(13) := '1';
MAPPED_DATA(21) := '1';
end if;
end if;
if CGROM_DATA(4) = '0' then
MAPPED_DATA(4) := '0';
MAPPED_DATA(12) := '0';
MAPPED_DATA(20) := '0';
else
MAPPED_DATA(4) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(12) := '1';
MAPPED_DATA(20) := '1';
end if;
end if;
if CGROM_DATA(3) = '0' then
MAPPED_DATA(3) := '0';
MAPPED_DATA(11) := '0';
MAPPED_DATA(19) := '0';
else
MAPPED_DATA(3) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(11) := '1';
MAPPED_DATA(19) := '1';
end if;
end if;
if CGROM_DATA(2) = '0' then
MAPPED_DATA(2) := '0';
MAPPED_DATA(10) := '0';
MAPPED_DATA(18) := '0';
else
MAPPED_DATA(2) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(10) := '1';
MAPPED_DATA(18) := '1';
end if;
end if;
if CGROM_DATA(1) = '0' then
MAPPED_DATA(1) := '0';
MAPPED_DATA(9) := '0';
MAPPED_DATA(17) := '0';
else
MAPPED_DATA(1) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(9) := '1';
MAPPED_DATA(17) := '1';
end if;
end if;
if CGROM_DATA(0) = '0' then
MAPPED_DATA(0) := '0';
MAPPED_DATA(8) := '0';
MAPPED_DATA(16) := '0';
else
MAPPED_DATA(0) := '1';
if CONFIG(MZ_KC) = '1' then
MAPPED_DATA(8) := '1';
MAPPED_DATA(16) := '1';
end if;
end if;
-- If invert option selected, invert green.
--
if CONFIG(MZ_A) = '1' and DISPLAY_INVERT = '1' then
MAPPED_DATA(7 downto 0) := not MAPPED_DATA(7 downto 0);
end if;
end if;
else
MAPPED_DATA := (others => '0');
end if;
-- Graphics ram enabled?
--
if CONFIG(GRAMDISABLE) = '0' and GRAM_MODE(5) = '0' then
-- Merge in the graphics data using defined mode.
--
case GRAM_MODE(7 downto 6) is
when "00" =>
MAPPED_DATA := MAPPED_DATA or XFER_GRAM_DATA;
when "01" =>
MAPPED_DATA := MAPPED_DATA and XFER_GRAM_DATA;
when "10" =>
MAPPED_DATA := MAPPED_DATA nand XFER_GRAM_DATA;
when "11" =>
MAPPED_DATA := MAPPED_DATA xor XFER_GRAM_DATA;
end case;
end if;
-- Assign the video data to the framebuffer input.
XFER_MAPPED_DATA <= MAPPED_DATA;
XFER_CYCLE := 4;
-- Commence write of mapped data.
when 4 =>
XFER_WEN <= '1';
XFER_CYCLE := 5;
-- Complete write and update address.
when 5 =>
-- Write cycle to framebuffer finished.
XFER_WEN <= '0';
XFER_CYCLE := 6;
-- Update the counters.
when 6 =>
-- For each source character, we generate 8 lines in the frame buffer. Thus we increment the
-- source character sub-row which addresses a different portion of the Character Generator Rom
-- and store that data into the frame buffer. Once we get to the last sub-row address,
-- increment the source and destination address parameters.
if XFER_DST_SUBROW < MAX_SUBROW-1 then
XFER_DST_SUBROW := XFER_DST_SUBROW + 1;
XFER_CYCLE := 1;
else
-- Increment Source Column/Row
if XFER_SRC_COL < MAX_COLUMN - 1 then
XFER_SRC_COL := XFER_SRC_COL + 1;
else
XFER_SRC_COL := 0;
XFER_SRC_ROW := XFER_SRC_ROW + 1;
end if;
-- Increment Destination Column/Row - reset subrow to 0
XFER_DST_SUBROW := 0;
if XFER_DST_COL < MAX_COLUMN - 1 then
XFER_DST_COL := XFER_DST_COL + 1;
else
XFER_DST_COL := 0;
XFER_DST_ROW := XFER_DST_ROW + 1;
end if;
XFER_CYCLE := 0;
end if;
end case;
end if;
-- On a new cycle, reset the transfer parameters.
--
if V_COUNT = V_DISPLAY_START then
XFER_VRAM_ADDR <= (others => '0');
XFER_DST_ADDR <= (others => '0');
XFER_CGROM_ADDR <= (others => '0');
XFER_SRC_COL := 0;
XFER_SRC_ROW := 0;
XFER_DST_COL := 0;
XFER_DST_SUBROW := 0;
XFER_DST_ROW := 0;
XFER_CYCLE := 0;
XFER_WEN <= '0';
end if;
end if;
end process;
--
-- Blank & Sync Generation
--
process( RST_n, CLKBUS(CKVIDEO), H_LINE_END, V_LINE_END )
variable configured : integer range 0 to 2000000;
variable FB_COL : integer range 0 to 80;
variable FB_LINE : integer range 0 to 200;
begin
-- On reset, initialise parameters then set wait timer running.
--
if RST_n='0' then
FB_COL := 0;
FB_LINE := 0;
FB_ADDR <= (others => '0');
-- In order to ensure the correct machine configuration has been latched, wait a period of
-- time before loading the display configuration.
--
configured := 2000000;
elsif CLKBUS(CKVIDEO)'event and CLKBUS(CKVIDEO)='1' then
-- When the time period for allowing the machine configuration to be latched has expired, set the
-- display configuration according to machine/display model.
if configured = 1 then
-- MZ80K/C/1200/A machines have a monochrome 60Hz display, with scan of 512 x 260 for a 320x200 viewable area.
if CONFIG(NORMAL) = '1' then
MAX_COLUMN <= 40; -- 40 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 320;
H_BLNK_START <= 320;
H_BLNK_END <= 486;
H_SYNC_START <= 320 + 73;
H_SYNC_END <= 320 + 73 + 45;
H_LINE_END <= 511;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 19;
V_SYNC_END <= 200 + 19 + 4;
V_LINE_END <= 259;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- MZ80K/C/1200/A machines with an adapted monochrome 60Hz display, with scan of 1024 x 260 for a 640x200 viewable area.
elsif CONFIG(NORMAL80) = '1' then
MAX_COLUMN <= 80; -- 80 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 640;
H_BLNK_START <= 640;
H_BLNK_END <= 972;
H_SYNC_START <= 640 + 146;
H_SYNC_END <= 640 + 146 + 90;
H_LINE_END <= 1023;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 19;
V_SYNC_END <= 200 + 19 + 4;
V_LINE_END <= 259;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- MZ700 has a colour 50Hz display, with scan of 568 x 320 for a 320x200 viewable area.
elsif CONFIG(COLOUR) = '1' and CONFIG(MZ700) = '1' then
MAX_COLUMN <= 40; -- 40 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 320;
H_BLNK_START <= 320;
H_BLNK_END <= 548;
H_SYNC_START <= 320 + 80;
H_SYNC_END <= 320 + 80 + 40;
H_LINE_END <= 567;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 45;
V_SYNC_END <= 200 + 45 + 3;
V_LINE_END <= 311;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- MZ700 has colour 50Hz display, with scan of 1136 x 320 for a 640x200 viewable area.
elsif CONFIG(COLOUR80) = '1' and CONFIG(MZ700) = '1' then
MAX_COLUMN <= 80; -- 80 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 640;
H_BLNK_START <= 640;
H_BLNK_END <= 1096;
H_SYNC_START <= 640 + 160;
H_SYNC_END <= 640 + 160 + 80;
H_LINE_END <= 1134;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 45;
V_SYNC_END <= 200 + 45 + 3;
V_LINE_END <= 311;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- MZ80K/C/1200/A machines with MZ700 style colour @ 60Hz display, with scan of 512 x 260 for a 320x200 viewable area.
elsif CONFIG(COLOUR) = '1' then
MAX_COLUMN <= 40; -- 40 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 320;
H_BLNK_START <= 320;
H_BLNK_END <= 486;
H_SYNC_START <= 320 + 73;
H_SYNC_END <= 320 + 73 + 45;
H_LINE_END <= 511;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 19;
V_SYNC_END <= 200 + 19 + 4;
V_LINE_END <= 259;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- MZ80K/C/1200/A machines with MZ700 style colour @ 60Hz display, with scan of 1024 x 260 for a 640x200 viewable area.
elsif CONFIG(COLOUR80) = '1' then
MAX_COLUMN <= 80; -- 80 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(H_LINE_END, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 640;
H_BLNK_START <= 640;
H_BLNK_END <= 972;
H_SYNC_START <= 640 + 146;
H_SYNC_END <= 640 + 146 + 90;
H_LINE_END <= 1023;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 19;
V_SYNC_END <= 200 + 19 + 4;
V_LINE_END <= 259;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
-- Default set for unrecognised machine id.
--
else
MAX_COLUMN <= 40; -- 40 x 25 character display area.
MAX_ROW <= 25;
MAX_SUBROW <= 8;
H_COUNT <= to_unsigned(0, 11);
V_COUNT <= to_unsigned(0, 11); --(others => '0');
H_BLANKi <= '1';
V_BLANKi <= '0';
BLNK <= '0';
H_SYNC_ni <= '1';
V_SYNC_ni <= '1';
H_DISPLAY_START <= 0;
H_DISPLAY_END <= 320;
H_BLNK_START <= 320;
H_BLNK_END <= 486;
H_SYNC_START <= 320 + 73;
H_SYNC_END <= 320 + 73 + 45;
H_LINE_END <= 511;
V_DISPLAY_START <= 0;
V_DISPLAY_END <= 200;
V_SYNC_START <= 200 + 19;
V_SYNC_END <= 200 + 19 + 4;
V_LINE_END <= 259;
FB_ADDR <= (others => '0');
SR_G_DATA <= (others => '0');
SR_R_DATA <= (others => '0');
SR_B_DATA <= (others => '0');
end if;
configured := 0;
elsif configured = 0 then
-- Activate/deactivate signals according to pixel position.
--
if H_COUNT = H_DISPLAY_START then H_BLANKi <= '0'; end if;
if H_COUNT = H_DISPLAY_END then H_BLANKi <= '1'; end if;
if H_COUNT = H_BLNK_START then BLNK <= '1'; end if;
if H_COUNT = H_BLNK_END then BLNK <= '0'; end if;
if H_COUNT = H_SYNC_END then H_SYNC_ni <= '1'; end if;
if H_COUNT = H_SYNC_START then H_SYNC_ni <= '0'; end if;
if V_COUNT = V_DISPLAY_START then V_BLANKi <= '0'; end if;
if V_COUNT = V_DISPLAY_END then V_BLANKi <= '1'; end if;
if V_COUNT = V_SYNC_START then V_SYNC_ni <= '0'; end if;
if V_COUNT = V_SYNC_END then V_SYNC_ni <= '1'; end if;
-- During the active display area, clock out from the frame buffer the pixel information.
--
if H_COUNT < H_DISPLAY_END and V_COUNT < V_DISPLAY_END then
-- Data is stored in the frame buffer in bytes, 1 bit per pixel x 8 and 3 colors, thus 1 x 8 x 3 or 24 bit. Read
-- out the values into shift registers to be serialised.
--
if H_COUNT(2 downto 0) = "000" then
SR_G_DATA <= DISPLAY_DATA(7 downto 0);
SR_R_DATA <= DISPLAY_DATA(15 downto 8);
SR_B_DATA <= DISPLAY_DATA(23 downto 16);
end if;
-- One clock cycle after loading the shift registers, we update the column position so the next memory location
-- address can be calculated and presented to RAM so new data is available for next shift register load.
--
if H_COUNT(2 downto 0) = "001" then
if FB_COL < MAX_COLUMN - 1 then
FB_COL := FB_COL + 1;
elsif FB_COL = MAX_COLUMN -1 then
FB_COL := 0;
FB_LINE := FB_LINE + 1;
end if;
end if;
-- Using the horizontal counter in sets of 8, when 0 we load the shift register from memory and bit 7 is immediately
-- available as a pixel, then all other horizontal counter values, ie. 1 - 7, we shift the bits along to be output
-- as a video signal.
--
if H_COUNT(2 downto 0) /= "000" then
SR_G_DATA <= SR_G_DATA(6 downto 0) & '0';
SR_R_DATA <= SR_R_DATA(6 downto 0) & '0';
SR_B_DATA <= SR_B_DATA(6 downto 0) & '0';
end if;
end if;
-- The column and row position is reset to home once we reach the end of the active display.
--
if H_COUNT = H_LINE_END and V_COUNT = V_DISPLAY_END then
FB_COL := 0;
FB_LINE := 0;
end if;
-- Calculate the new frame buffer address, based on Line x Col format.
--
FB_ADDR <= std_logic_vector(to_unsigned((FB_LINE * MAX_COLUMN) + FB_COL, 14));
-- Horizontal/Vertical counters are updated each clock cycle to accurately track pixel/timing.
--
if H_COUNT = H_LINE_END then
H_COUNT <= (others => '0');
if V_COUNT = V_LINE_END then
V_COUNT <= (others => '0');
else
V_COUNT <= V_COUNT + 1;
end if;
else
H_COUNT <= H_COUNT + 1;
end if;
else
-- Decrement configured timer to implement Reset -> load config delay.
--
configured := configured -1;
end if;
end if;
end process;
--
-- Control Registers
-- MZ1200/80A: INVERT display, accessed at E014
-- SCROLL display, accessed at E200 - E2FF, the address determines the offset.
-- EA,<val> sets the graphics mode. 7/6 = Operator (00=OR,01=AND,10=NAND,11=XOR), 5=GRAM Output Enable, 4 = VRAM Output Enable, 3/2 = Write mode (00=Page 1:Red, 01=Page 2:Green, 10=Page 3:Blue, 11=Indirect), 1/0=Read mode (00=Page 1:Red, 01=Page2:Green, 10=Page 3:Blue, 11=Not used).
-- EB,<val> sets the Red bit mask (1 bit = 1 pixel, 8 pixels per byte).
-- EC,<val> sets the Green bit mask (1 bit = 1 pixel, 8 pixels per byte).
-- ED,<val> sets the Blue bit mask (1 bit = 1 pixel, 8 pixels per byte).
--
process( RST_n, CLKBUS(CKCPU) ) begin
if RST_n='0' then
DISPLAY_INVERT <= '0';
OFFSET_ADDR <= (others => '0');
GRAM_MODE <= "00001100";
GRAM_RED_WRITER <= (others => '1');
GRAM_GREEN_WRITER <= (others => '1');
GRAM_BLUE_WRITER <= (others => '1');
elsif CLKBUS(CKCPU)'event and CLKBUS(CKCPU)='1' then
if CS_INVERT_n='0' and T80_RD_n='0' then
DISPLAY_INVERT <= T80_MA(0);
end if;
if CS_SCROLL_n='0' and T80_RD_n='0' then
if CONFIG(NORMAL80) = '1' or CONFIG(COLOUR80) = '1' then
OFFSET_ADDR <= (others => '0');
else
OFFSET_ADDR <= T80_A(7 downto 0);
end if;
end if;
if CS_IO_EA_n='0' and T80_WR_n='0' then
GRAM_MODE <= T80_DI;
end if;
if CS_IO_EB_n='0' and T80_WR_n='0' then
GRAM_RED_WRITER <= T80_DI;
end if;
if CS_IO_EC_n='0' and T80_WR_n='0' then
GRAM_GREEN_WRITER <= T80_DI;
end if;
if CS_IO_ED_n='0' and T80_WR_n='0' then
GRAM_BLUE_WRITER <= T80_DI;
end if;
end if;
end process;
-- Enable Video Wait States - Original design has wait states inserted into the cycle if the CPU accesses the VRAM during display. In the updated design, the VRAM
-- is copied into a framebuffer during the Vertical Blanking period so no wait states are needed. To keep consistency with the original design (for programs which depend on it),
-- the wait states can be enabled by configuration.
--
process( T80_MREQ_n ) begin
if T80_MREQ_n'event and T80_MREQ_n='0' then
BLNK_MEMACCESS <= BLNK;
end if;
end process;
--
-- Extend wait by 1 cycle
process( CLKBUS(CKCPU) ) begin
if CLKBUS(CKCPU)'event and CLKBUS(CKCPU)='1' then
WAITii_n <= WAITi_n;
end if;
end process;
--
-- PCG Access Registers
--
-- E010: PCG_DATA (byte to describe 8-pixel row of a character)
-- E011: PCG_ADDR (offset in the PCG in 8-pixel row unit) -> up to 256/8 = 32 characters
-- E012: PCG_CTRL
-- bit 0-1: character selector -> (PCG_ADDR + 256*(PCG_CTRL&3)) -> address in the range of the upper 128 characters font
-- bit 2 : font selector -> PCG_CTRL&2 == 0 -> 1st font else 2nd font
-- bit 3 : select which font for display
-- bit 4 : use programmable font for display
-- bit 5 : set programmable upper font -> PCG_CTRL&20 == 0 -> fixed upper 128 characters else programmable upper 128 characters
-- So if you want to change a character pattern (only doable in the upper 128 characters of a font), you need to:
-- - set bit 5 to 1 : PCG_CTRL[5] = 1
-- - set the font to select : PCG_CTRL[2] = font_number
-- - set the first row address of the character: PCG_ADDR[0..7] = row[0..7] and PCG_CTRL[0..1] = row[8..9]
-- - set the 8 pixels of the row in PCG_DATA
--
process( RST_n, CLKBUS(CKCPU) ) begin
if RST_n = '0' then
CGRAM_ADDR <= (others=>'0');
PCG_DATA <= (others=>'0');
CGRAM_WE_n <= '1';
elsif CLKBUS(CKCPU)'event and CLKBUS(CKCPU)='1' then
if CS_PCG_n = '0' and T80_WR_n = '0' then
-- Set the PCG Data to program to RAM.
if T80_A(1 downto 0) = "00" then
PCG_DATA <= T80_DI;
end if;
-- Set the PCG Address in RAM.
if T80_A(1 downto 0) = "01" then
CGRAM_ADDR(7 downto 0) <= T80_DI;
end if;
-- Set the PCG Control register.
if T80_A(1 downto 0) = "10" then
CGRAM_ADDR(11 downto 8) <= (T80_DI(2) and CONFIG(MZ_A)) & '1' & T80_DI(1 downto 0);
CGRAM_WE_n <= not T80_DI(4);
CGRAM_SEL <= T80_DI(5);
end if;
end if;
end if;
end process;
--
-- CPU / RAM signals and selects.
--
WAITi_n <= '0' when CS_D_n = '0' and BLNK_MEMACCESS = '0' and BLNK = '0' and (CONFIG(MZ_A) = '1' or CONFIG(MZ700) = '1')
else '1';
T80_WAIT_n <= WAITi_n and WAITii_n when CONFIG(VRAMWAIT) = '1'
else '1';
T80_MA <= "00" & T80_A(9 downto 0) when CONFIG(MZ_KC) = '1'
else
T80_A(11 downto 0);
-- Program Character Generator RAM. E010 - Write cycle (Read cycle = reset memory swap).
CS_PCG_n <= '0' when CS_E_n = '0' and T80_A(10 downto 4) = "0000001"
else '1'; -- D010 -> D01f
-- Invert display register. E014/E015
CS_INVERT_n <= '0' when CS_E_n = '0' and CONFIG(MZ_A) = '1' and T80_MA(11 downto 9) = "000" and T80_MA(4 downto 2) = "101"
else '1';
-- Scroll display register. E200 - E2FF
CS_SCROLL_n <= '0' when CS_E_n = '0' and T80_A(11 downto 8)="0010" and CONFIG(MZ_A)='1'
else '1';
-- EA,<val> sets the graphics mode. 7/6 = Operator (00=OR,01=AND,10=NAND,11=XOR),
-- 5 = GRAM Output Enable, 4 = VRAM Output Enable,
-- 3/2 = Write mode (00=Page 1:Red, 01=Page 2:Green, 10=Page 3:Blue, 11=Indirect),
-- 1/0 = Read mode (00=Page 1:Red, 01=Page2:Green, 10=Page 3:Blue, 11=Not used).
CS_IO_EA_n <= '0' when CS_IO_GRAM_n = '0' and T80_A(2 downto 0) = "010"
else '1';
-- EB,<val> sets the Red bit mask (1 bit = 1 pixel, 8 pixels per byte).
CS_IO_EB_n <= '0' when CS_IO_GRAM_n = '0' and T80_A(2 downto 0) = "011"
else '1';
-- EC,<val> sets the Green bit mask (1 bit = 1 pixel, 8 pixels per byte).
CS_IO_EC_n <= '0' when CS_IO_GRAM_n = '0' and T80_A(2 downto 0) = "100"
else '1';
-- ED,<val> sets the Blue bit mask (1 bit = 1 pixel, 8 pixels per byte).
CS_IO_ED_n <= '0' when CS_IO_GRAM_n = '0' and T80_A(2 downto 0) = "101"
else '1';
T80_DO <= VRAM_VIDEO_DATA when T80_RD_n = '0' and CS_D_n = '0'
else
GRAM_VIDEO_DATA when T80_RD_n = '0' and CS_G_n = '0'
else
(others=>'0');
VRAM_ADDR <= T80_MA(10 downto 0) & T80_MA(11) when IOCTL_CS_VRAM_n = '1'
else
IOCTL_ADDR(10 downto 0) & IOCTL_ADDR(11);
VRAM_DATA_IN <= T80_DI when IOCTL_CS_VRAM_n = '1'
else
IOCTL_DOUT(7 downto 0);
VWEN <= '1' when T80_WR_n='0' and CS_D_n = '0'
else '0';
VRAM_WEN <= VWEN when IOCTL_CS_VRAM_n = '1'
else
IOCTL_WEN_VRAM;
VRAM_VIDEO_DATA <= VRAM_DATA_OUT when IOCTL_CS_VRAM_n = '1'
else
(others=>'0');
IOCTL_DIN_VRAM <= VRAM_DATA_OUT when IOCTL_CS_VRAM_n = '0'
else
(others=>'0');
VRAM_CLK <= CLKBUS(CKMEM) when IOCTL_CS_VRAM_n = '1'
else
IOCTL_CLK;
-- CGROM Data to CG RAM, either ROM -> RAM copy or Z80 provides map.
--
CGRAM_DI <= CGROM_DO when CGRAM_SEL = '1' -- Data from ROM
else
PCG_DATA when CGRAM_SEL = '0' -- Data from PCG
else (others=>'0');
CGRAM_WEN <= not (CGRAM_WE_n or CS_PCG_n) and not T80_WR_n;
--
-- Font select
--
CGROM_DATA <= CGROM_DO when CONFIG(PCGRAM)='0'
else
PCG_DATA when CS_PCG_n='0' and T80_A(1 downto 0)="10" and T80_WR_n='0'
else
CGRAM_DO when CONFIG(PCGRAM)='1'
else (others => '1');
CG_ADDR <= CGRAM_ADDR(11 downto 0) when CGRAM_WE_n = '0'
else XFER_CGROM_ADDR;
CGROM_BANK <= "0000" when CONFIG(MZ80K) = '1'
else
"0001" when CONFIG(MZ80C) = '1'
else
"0010" when CONFIG(MZ1200) = '1'
else
"0011" when CONFIG(MZ80A) = '1'
else
"0100" when CONFIG(MZ700) = '1' and XFER_CGROM_ADDR(11) = '0'
else
"0101" when CONFIG(MZ700) = '1' and XFER_CGROM_ADDR(11) = '1'
else
"0110" when CONFIG(MZ800) = '1' and XFER_CGROM_ADDR(11) = '0'
else
"0111" when CONFIG(MZ800) = '1' and XFER_CGROM_ADDR(11) = '1'
else
"1000" when CONFIG(MZ80B) = '1'
else
"1001" when CONFIG(MZ2000) = '1'
else
"1111";
-- As the Graphics RAM is an odd size, 16384 x 3 colour planes, it has to be in 3 seperate 16K blocks to avoid wasting memory (or having it synthesized away),
-- thus there are 3 sets of signals, 1 per colour.
--
GRAM_ADDR <= T80_A(13 downto 0) when IOCTL_CS_GRAM_n = '1'
else
IOCTL_ADDR(13 downto 0);
-- direct writes when accessing individual pages.
GRAM_DATA_IN_R <= T80_DI when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "00"
else
T80_DI and GRAM_RED_WRITER when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "11"
else
IOCTL_DOUT(7 downto 0) when IOCTL_ADDR(15 downto 14) = "00"
else
(others=>'0');
-- direct writes when accessing individual pages.
GRAM_DATA_IN_G <= T80_DI when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "01"
else
T80_DI and GRAM_GREEN_WRITER when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "11"
else
IOCTL_DOUT(7 downto 0) when IOCTL_ADDR(15 downto 14) = "01"
else
(others=>'0');
-- direct writes when accessing individual pages.
GRAM_DATA_IN_B <= T80_DI when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "10"
else
T80_DI and GRAM_BLUE_WRITER when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(3 downto 2) = "11"
else
IOCTL_DOUT(7 downto 0) when IOCTL_ADDR(15 downto 14) = "10"
else
(others=>'0');
GWEN_R <= '1' when T80_WR_n = '0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "00"
else
'1' when T80_WR_n = '0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "11"
else
'0';
GRAM_WEN_R <= GWEN_R when IOCTL_CS_GRAM_n = '1'
else
IOCTL_WEN_GRAM_R;
GWEN_G <= '1' when T80_WR_n='0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "01"
else
'1' when T80_WR_n='0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "11"
else
'0';
GRAM_WEN_G <= GWEN_G when IOCTL_CS_GRAM_n = '1'
else
IOCTL_WEN_GRAM_G;
GWEN_B <= '1' when T80_WR_n='0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "10"
else
'1' when T80_WR_n='0' and CS_G_n = '0' and GRAM_MODE(3 downto 2) = "11"
else
'0';
GRAM_WEN_B <= GWEN_B when IOCTL_CS_GRAM_n = '1'
else
IOCTL_WEN_GRAM_B;
GRAM_VIDEO_DATA <= GRAM_DATA_OUT_R when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(1 downto 0) = "00"
else
GRAM_DATA_OUT_G when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(1 downto 0) = "01"
else
GRAM_DATA_OUT_B when IOCTL_CS_GRAM_n = '1' and GRAM_MODE(1 downto 0) = "10"
else
(others=>'0');
IOCTL_DIN_GRAM <= GRAM_DATA_OUT_R when IOCTL_CS_GRAM_n = '0' and GRAM_MODE(1 downto 0) = "00"
else
GRAM_DATA_OUT_G when IOCTL_CS_GRAM_n = '0' and GRAM_MODE(1 downto 0) = "01"
else
GRAM_DATA_OUT_B when IOCTL_CS_GRAM_n = '0' and GRAM_MODE(1 downto 0) = "10"
else
(others=>'0');
GRAM_CLK <= CLKBUS(CKMEM) when IOCTL_CS_GRAM_n = '1'
else
IOCTL_CLK;
--
-- HPS Access - match whole address, additional LE but easier to read.
--
IOCTL_WEN_VRAM <= '1' when IOCTL_CS_VRAM_n = '0' and IOCTL_WR = '1'
else '0';
IOCTL_WEN_GRAM_R <= '1' when IOCTL_CS_GRAM_n = '0' and IOCTL_WR = '1' and IOCTL_ADDR(15 downto 14) = "00"
else '0';
IOCTL_WEN_GRAM_G <= '1' when IOCTL_CS_GRAM_n = '0' and IOCTL_WR = '1' and IOCTL_ADDR(15 downto 14) = "01"
else '0';
IOCTL_WEN_GRAM_B <= '1' when IOCTL_CS_GRAM_n = '0' and IOCTL_WR = '1' and IOCTL_ADDR(15 downto 14) = "10"
else '0';
IOCTL_WEN_CGROM <= '1' when IOCTL_CS_CGROM_n = '0' and IOCTL_WR = '1'
else '0';
IOCTL_WEN_CGRAM <= '1' when IOCTL_CS_CGRAM_n = '0' and IOCTL_WR = '1'
else '0';
IOCTL_CS_VRAM_n <= '0' when IOCTL_ADDR(24 downto 16) = "000000100"
else '1';
IOCTL_CS_GRAM_n <= '0' when IOCTL_ADDR(24 downto 16) = "000000100"
else '1';
IOCTL_CS_CGROM_n <= '0' when IOCTL_ADDR(24 downto 15) = "0000001110"
else '1';
IOCTL_CS_CGRAM_n <= '0' when IOCTL_ADDR(24 downto 11) = "00000100000000"
else '1';
IOCTL_DIN <= X"00" & IOCTL_DIN_VRAM when IOCTL_CS_VRAM_n = '0' and IOCTL_RD = '1'
else
X"00" & IOCTL_DIN_GRAM when IOCTL_CS_GRAM_n = '0' and IOCTL_RD = '1'
else
X"00" & IOCTL_DIN_CGROM when IOCTL_CS_CGROM_n = '0' and IOCTL_RD = '1'
else
X"00" & IOCTL_DIN_CGRAM when IOCTL_CS_CGRAM_n = '0' and IOCTL_RD = '1'
else
(others=>'0');
--
-- Video Output Signals
--
VBLANK <= V_BLANKi;
HBLANK <= H_BLANKi;
VSYNC_n <= V_SYNC_ni;
HSYNC_n <= H_SYNC_ni;
ROUT <= SR_R_DATA(7) when H_BLANKi='0' or VGATE_n='1'
else
'0';
GOUT <= SR_G_DATA(7) when H_BLANKi='0' or VGATE_n='1'
else
'0';
BOUT <= SR_B_DATA(7) when H_BLANKi='0' or VGATE_n='1'
else
'0';
end RTL;