FPGABee v3.0

Colour and Extended PCG Graphics

Microbee Colour Support

The Microbee supports colour through a block of RAM that stores one byte for each character. A colour is defined by 4 bits where the intensity bit increases the value of all three colour channels.

  • Bit 0 - Foreground Red component
  • Bit 1 - Foreground Green component
  • Bit 2 - Foreground Blue component
  • Bit 3 - Foreground Intensity mode
  • Bit 4 - Background Red component
  • Bit 5 - Background Green component
  • Bit 6 - Background Blue component
  • Bit 7 - Background Intensity mode

Colour RAM is accessed at 0xF800 - the same location as the PCG RAM, but is bank switched via port 8. When bit 6 of of port 8 is 1, 0xF800 to 0xFFFF maps to the colour RAM. When it's zero, that area of RAM accesses the PCG RAM.

Added Colour RAM

In the FPGABee version 1, I was using the Xilinx Core Generator to generate block RAMs. Since then I've learned that the tool-chain can "infer" block RAMs from VHDL code the conforms to a certain layout. As such, I've put together a couple of simple generic entities that make instantiating different block RAMs easy.

Here's my implementation of a parameterized dual port block RAM where one port is read/write and one is read only:

-- Entity RamDualPort

library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
use std.textio.all;
use ieee.std_logic_textio.all;

entity RamDualPort is
		ADDR_WIDTH : integer;
		DATA_WIDTH : integer := 8
		-- Port A
		clock_a : in std_logic;
		addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);
		din_a : in std_logic_vector(DATA_WIDTH-1 downto 0);
		dout_a : out std_logic_vector(DATA_WIDTH-1 downto 0);
		wr_a : in std_logic;

		-- Port B
		clock_b : in std_logic;
		addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0);
		dout_b : out std_logic_vector(DATA_WIDTH-1 downto 0)
end RamDualPort;

architecture behavior of RamDualPort is 
	constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
	type mem_type is array(0 to MEM_DEPTH) of std_logic_vector(DATA_WIDTH-1 downto 0);
	shared variable ram : mem_type;

	process (clock_a)
		if rising_edge(clock_a) then

			if wr_a = '1' then
				ram(to_integer(unsigned(addr_a))) := din_a;
			end if;

			dout_a <= ram(to_integer(unsigned(addr_a)));

		end if;
	end process;

	process (clock_b)
		if rising_edge(clock_b) then

			dout_b <= ram(to_integer(unsigned(addr_b)));

		end if;
	end process;


Now instead of having to run the slow Core Generator, I can create block RAMs directly in code like this:

-- 2k Color RAM at 0xF800 - 0xFFFF
color_ram : entity work.RamDualPort
	-- port A for CPU read/write
	clock_a => z80_clock,
	wr_a => color_ram_wea,
	addr_a => translated_address(10 downto 0),
	din_a => z80_dout,
	dout_a => color_ram_dout,

	-- port B for 6545, read-only
	clock_b => pixel_clock,
	addr_b => char_ram_addr_b,
	dout_b => color_ram_dout_b

Other than that, setting up the colour RAM was identical to how the other memory components are configured.

Supporting Colour in the Video Controller

With the colour RAM in place, the video controller just needed to be updated to use it. Firstly, the colour RAM is retrieved at the same time as the character, and uses the same address. Unlike the retrieved character that is used in the next clock cycle, the colour RAM isn't required until the cycle after next, so we need to delay it by one clock - similarly to how the PCG flag is delayed by 1 clock:

process (pixel_clock)
	if (rising_edge(pixel_clock)) then
		pcg_select <= char_ram_dout(7);
		color_delayed <= color_ram_dout;			-- delay colour info by one clock cycle
	end if;
end process;

Next, it needed to pick out the appropriate fore/back colour nibble depending on whether the pixel should be "lit" or not:

-- Select the appropriate half of the color byte
color_nibble <= color_delayed(3 downto 0) when (pixel='1') else color_delayed(7 downto 4);

And, finally generate the appropriate colour:

-- Translate color
vgaRed <= color_nibble(0) & color_nibble(3) & "0";
vgaGreen <= color_nibble(1) & color_nibble(3) & "0";
vgaBlue <= color_nibble(2) & color_nibble(3);

(Note that I'm only using the two most significant colour bits of each colour component - the Nexys only supports 3-3-2 colour depth).

Extended PCG RAM and Attribute RAM

The Premium model Microbee's also supported extended PCG RAM with most being fitted with 16K (as opposed to 2K) and some up to 32K.

The PCG RAM was accessed via a set of 2K banks, and the bank to be used for a particular character is selected through another block of attribute RAM where the bottom 4 bits specify the bank. The attribute RAM is located at 0xF000 (just like the character RAM) and is bank switched via bit 4 of port 0x1C.

Furthermore, bit 7 of port 0x1C controls whether the extended PCG graphics are enabled at all - so that older software would continue to run without modification.

From the CPU side this all boiled down to more logic to decide which block RAMs should be used for a particular memory access.

From the video controller side of things it meant an extra memory look-up into the attribute RAM (in the same memory cycle as the character and colour RAM), followed by selecting the appropriate addressing for the PCG RAM based on whether the extended graphics are enabled and which bank is selected by the attribute RAM.


After implementing all of the above I was ready to start testing it, but not ready to boot up the OS, so I wrote a simple "boot ROM" program to put the video controller through it's paces. It took a little debugging, but soon had it working. Here are some screen shots of a few of the different modes.

The numbers at the bottom show the screen width in characters, screen height in rows and character height in scan lines.

The little numbers 1 through 7 at the top of the second photo are each from a different PCG bank confirming the attribute and PCG RAM is working. The tests also flipped extended graphics on/off to confirm they reverted to bank 0 (as in the first photo).


Video Controller Complete

So that's it for the video controller geometry and colour support improvements. Next I'll be looking at the Premium's main memory bank switching.

Subscribe for more like this. No spam, just fun tech stuff :)

Or, find me on Twitter: @toptensoftware.