FPGABee v3.0

CRTC Cursor

In the last article I managed to get FPGABee to show the Microworld Basic boot screen. I didn't mention though that it wasn't quite right. Although the welcome message was appearing the command prompt '>' wasn't and I didn't know why.

Debugging an Infinite Loop

My best guess was that the CPU was stuck in a loop somewhere with the Basic ROM waiting for something from the hardware that wasn't yet implemented.

To track this down I brought in a previous experimental circuit for driving the Nexys3's 4-Digit 7-Segment LED display and hooked up one of the on-board buttons to have it show the current address the Z80 was executing at. The Z80 control line M1 goes active when the CPU is in the first phase of executing an instruction. By watching for a memory request at the same time, I could pick up the current instruction address and display it.

I then disassembled the BASIC ROM and took a look at what it was doing and sure enough it was try to read from the keyboard, waiting for a response that just never going to come. Conveniently, this directly related to what I had to work on next anyway... the cursor.

The 6545 CRTC Chip

In the previous article I also mentioned the 6545 CRTC chip which forms the final part of the Microbee's video controller. On a real Microbee it's responsible for doing much of the work already already done by FPGABee's VGA controller including generating video RAM address lines and the video vertical and horizontal sync signals.

The 6545 however also implements a number of registers that control things like the cursor position and shape, character size metrics and screen dimensions. In FPGABee the character size and screen dimensions are currently hard wired and the few programs that manipulate these settings won't work correctly.

But finally and more interestingly, the 6545 chip also has support for a light pen - but the Microbee doesn't use it for that, it uses it for the keyboard - which we'll come to later.

The interface to a 6545 chip is a bit weird - it has:

  • Approximately 16 internal 8-bit registers
  • A read-only status register
  • A bi-directional 8-bit data bus
  • A read/write control signal
  • A register/address select signal
  • and no address lines.

and you use it like this:

  • To set a register you write the register number to the data bus with the address signal active and then you write the register value with the address signal in-active.
  • To read a register you write the register number to the data bus with the address signal active and then you read the register value with the address signal in-active.
  • To read the status register you read with the address signal in-active.

This is all wired up to the Z80 on two I/O ports so that

  • Writing to port 0x0C writes a register number
  • Reading or writing port 0x0D reads or writes the register selected on port 0x0C
  • Reading from port 0x0C reads the status register

Implementing FPGABee's 6545

So far everything I'd done on FPGABee was pretty much just wiring existing components together. The 6545 was the first piece that I was going to have actually code some functionality for. To start with I created a new VHDL component:

entity Crtc6545 is
        clock: in STD_LOGIC;
        reset: in STD_LOGIC;
        wr: in STD_LOGIC;
        rs: in STD_LOGIC;
        din: in STD_LOGIC_VECTOR(7 downto 0);
        dout: out STD_LOGIC_VECTOR(7 downto 0);
end Crtc6545;

connected it the appropriate CPU's ports:

crtc_data_port <= '1' when (z80_addr(7 downto 0) = x"0d") else '0';
crtc_addr_port <= '1' when (z80_addr(7 downto 0) = x"0c") else '0';
crtc_wr <= '1' when (port_wr='1' and (crtc_addr_port='1' or crtc_data_port='1')) else '0';
crtc_rs <= '1' when (crtc_data_port='1') else '0';

z80_din <= 
        -- (others omitted)
        crtc_dout when (port_rd='1' and (crtc_addr_port='1' or crtc_data_port='1')) else

Crtc6545: entity work.Crtc6545 
        clock => z80_clock,
        reset => reset,
        wr => crtc_wr,
        rs => crtc_rs,
        din => z80_dout,
        dout => crtc_dout,

and implemented the minimal set of registers:

process(clock, reset)
	if (reset='1') then

		-- reset all registers
		reg_addr <= (others=>'0');
		reg_cursor_mode <= (others=>'0');
		reg_cursor_startline <= (others=>'0');
		reg_cursor_endline <= (others=>'0');
		reg_cursor_pos <= (others=>'0');

	elsif (clock'event and clock='1') then

		if (wr = '1') then

			if (rs = '1') then

				-- write to register
				case (reg_addr) is

					when "01010" =>
						-- cursor start scan line R10
						reg_cursor_startline <= din(4 downto 0);
						reg_cursor_mode <= din(6 downto 5);

					when "01011" =>
						-- cursor end scan line R11
						reg_cursor_endline <= din(4 downto 0);

					when "01110" =>
						-- cursor pos h R14
						reg_cursor_pos(13 downto 8) <= din(5 downto 0);

					when "01111" =>
						-- cursor pos l R15
						reg_cursor_pos(7 downto 0) <= din(7 downto 0);

					when others => 

				end case;


				-- write to address register
				reg_addr <= din(4 downto 0);

			end if;


			if (rs = '1') then

				-- read register
				case (reg_addr) is

					when "01110" =>
						-- cursor pos H R14
						dout <= "00" & reg_cursor_pos(13 downto 8);

					when "01111" =>
						-- cursor pos L R15
						dout <= reg_cursor_pos(7 downto 0);

					when "10000" =>
						-- light pen H R16
						dout <= "00" & reg_light_pen(13 downto 8);
						reg_status_light_pen_ready <= '0';

					when "10001" =>
						-- light pen L R17
						dout <= reg_light_pen(7 downto 0);
						reg_status_light_pen_ready <= '0';

					when others =>
						dout <= (others => '0');

				end case;


				-- read status register
				dout <= 	"10000000";

			end if;
		end if;
	end if;
end process;

See that last bit about reading the status register? That's what the Basic ROM was stuck waiting for so I just hard-wired it to '1' for now and voilĂ ... the command prompt now appeared as expected.

The Cursor

I now had everything I needed to implement the hardware cursor, except it was all in the wrong spot: I needed access to the cursor registers in the video controller code, not here in the 6545 implementation. I decided to moved all the video controller code into the 6545 component.

The next thing I needed was some sort of signal to handle the cursor blink. The 6545 has 4 blink modes that are controlled by bits 5 and 6 in register 10. The modes are always on, always off, blink slow and blink fast. For this I use a 22-bit counter that is incremented on each z80 clock tick. I then use the highest bit for the slow blink and the second highest bit for the fast blink:

-- Workout if cursor on/off/blinking
cursor_on <= '1' when (reg_cursor_mode="00") else									-- cursor on
					'0' when (reg_cursor_mode="01") else								-- cursor off
					reg_blink_counter(21) when (reg_cursor_mode="10") else		-- slow blink
					reg_blink_counter(20);													-- fast blink

Now for the actual pixel twiddling to make the cursor appear. First work out if the current pixel is "inside" the cursor. This simply compares the cursor position to the address in video RAM video ram address being draw by the video code and compares the current scan line to the scan line range in the cursor registers:

-- Work out if cursor is in the current character and cursor scanline range
cursor_pixel <= '1' when (
        (cursor_on = '1') and 
        ("0" & char_pixel_y>=reg_cursor_startline) and
        ("0" & char_pixel_y<=reg_cursor_endline) and
        (vram_addr= reg_cursor_pos(10 downto 0))
        ) else '0';

And finally, invert the current output pixel if the cursor covers it:

-- Generate the output pixel
pixel <= '0' when (video_blank = '1' or pixel_in_range = '0') else
            not current_pixel when (cursor_pixel = '1') else

An Exact Microbee Boot Screen.

I now had an exact clone of the Microbee boot screen - the welcome message, the command prompt and a little flashing cursor. By now, I'm coming to realise that all I needed to really bring this thing to life is a working keyboard.

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

Or, find me on Twitter: @toptensoftware.