mit41301
Published

Zilog Z80 SBC on a Cyclone IV FPGA

Z80 SBC with Z80 CORE, 8kB ROM for microsoft BASIC, 8kB RAM and UART all implemented inside the FPGA using VHDL. Altera Cyclone IV E used.

IntermediateFull instructions provided1 hour1,070
Zilog Z80 SBC on a Cyclone IV FPGA

Story

Read more

Schematics

EP4CE6E22C8 with 27 & 50 MHz oscillator with EPCS4 and CH340

SCH, pinout, examples,...

SCH

SCH with pinout

Code

Microcomputer VHDL code

VHDL
TOP ENTITY
library ieee;
use ieee.std_logic_1164.all;
use IEEE.STD_LOGIC_ARITH.all;
use IEEE.STD_LOGIC_UNSIGNED.all;

--
-- This defines the actual physical inputs and outputs I'm going to use.
--

entity Microcomputer is

  port(
    n_reset : in std_logic;
    clk : in std_logic;
    rxd1 : in std_logic;
    txd1 : out std_logic;
    leds : out std_logic_vector(7 downto 0)
  );

end Microcomputer;

--
-- This section defines signals, where are like the inputs and outputs of the module.
--

architecture struct of Microcomputer is

  signal n_WR : std_logic;
  signal n_RD : std_logic;
  signal cpuAddress : std_logic_vector(15 downto 0);
  signal cpuDataOut : std_logic_vector(7 downto 0);
  signal cpuDataIn : std_logic_vector(7 downto 0);

  signal basRomData : std_logic_vector(7 downto 0);
  signal internalRam1DataOut : std_logic_vector(7 downto 0);
  signal internalRam2DataOut : std_logic_vector(7 downto 0);
  signal interface1DataOut : std_logic_vector(7 downto 0);

  signal n_memWR : std_logic :='1';
  signal n_memRD : std_logic :='1';

  signal n_ioWR : std_logic :='1';
  signal n_ioRD : std_logic :='1';

  signal n_MREQ : std_logic :='1';
  signal n_IORQ : std_logic :='1';

  signal n_int1 : std_logic :='1';
  signal n_int2 : std_logic :='1';

  signal n_internalRam1CS : std_logic :='1';
  signal n_internalRam2CS : std_logic :='1';
  signal n_basRomCS : std_logic :='1';
  signal n_interface1CS : std_logic :='1';
  signal n_aaronCS : std_logic :='1';

  signal serialClkCount : std_logic_vector(15 downto 0);
  signal cpuClkCount : std_logic_vector(5 downto 0);
  signal cpuClock : std_logic;
  signal serialClock : std_logic;

begin

  --
  -- These entities are like importing other VHDL modules.
  -- The map() section maps the signals listed on _those_ VHDL modules to our
  -- own signals or to pins.
  --

  cpu1 : entity work.t80s
  generic map(mode => 1, t2write => 1, iowait => 0)
  port map(
    reset_n => n_reset,
    clk_n => cpuClock,
    wait_n => '1',
    int_n => '1',
    nmi_n => '1',
    busrq_n => '1',
    mreq_n => n_MREQ,
    iorq_n => n_IORQ,
    rd_n => n_RD,
    wr_n => n_WR,
    a => cpuAddress,
    di => cpuDataIn,
    do => cpuDataOut
  );

  rom1 : entity work.Z80_BASIC_ROM -- 8KB BASIC
  port map(
    address => cpuAddress(12 downto 0),
    clock => clk,
    q => basRomData
  );

  ram1: entity work.InternalRam4K
  port map(
    address => cpuAddress(11 downto 0),
    clock => clk,
    data => cpuDataOut,
    wren => not(n_memWR or n_internalRam1CS),
    q => internalRam1DataOut
  );

  io1 : entity work.bufferedUART
  port map(
    clk => clk,
    n_wr => n_interface1CS or n_ioWR,
    n_rd => n_interface1CS or n_ioRD,
    n_int => n_int1,
    regSel => cpuAddress(0),
    dataIn => cpuDataOut,
    dataOut => interface1DataOut,
    rxClock => serialClock,
    txClock => serialClock,
    rxd => rxd1,
    txd => txd1,
    n_cts => '0',
    n_dcd => '0'
  );

  io2 : entity work.aaron
  port map(
    n_wr => n_aaronCS or n_ioWR,
    dataIn => cpuDataOut,
    dataOut => leds,
    reset_n => n_reset
  );

  n_ioWR <= n_WR or n_IORQ;
  n_memWR <= n_WR or n_MREQ;
  n_ioRD <= n_RD or n_IORQ;
  n_memRD <= n_RD or n_MREQ;

  --
  -- This section performs the actual address decoding - it will raise or lower
  -- the chip select lines of the modules depending on the contents of the address
  -- bus.
  --

  -- These are active low so '0' means "enable this CS line"

  n_basRomCS <= '0' when cpuAddress(15 downto 13) = "000" else '1'; -- 8K at bottom of memory
  n_internalRam1CS <= '0' when cpuAddress(15 downto 12) = "0010" else '1'; -- 4K above that

  n_interface1CS <= '0' when cpuAddress(7 downto 1) = "1000000" and (n_ioWR='0' or n_ioRD = '0') else '1'; -- 2 Bytes $80-$81
  n_aaronCS <= '0' when cpuAddress(7 downto 1) = "1001000" and (n_ioWR='0' or n_ioRD = '0') else '1'; -- 2 Bytes $90-$91

  -- Connect the CPU and peripheral data lines when the respective CS line is low

  cpuDataIn <=
    interface1DataOut when n_interface1CS = '0' else
    basRomData when n_basRomCS = '0' else
    internalRam1DataOut when n_internalRam1CS= '0' else
    x"FF";

  --
  -- This is all to create a secondary, slower, clock for the UART to use.
  --

  serialClock <= serialClkCount(15);

  process (clk)
  begin
    if rising_edge(clk) then

      if cpuClkCount < 4 then -- 4 = 10MHz, 3 = 12.5MHz, 2=16.6MHz, 1=25MHz
        cpuClkCount <= cpuClkCount + 1;
      else
        cpuClkCount <= (others=>'0');
      end if;

      if cpuClkCount < 2 then -- 2 when 10MHz, 2 when 12.5MHz, 2 when 16.6MHz, 1 when 25MHz
        cpuClock <= '0';
      else
        cpuClock <= '1';
      end if;

      -- Serial clock DDS
      -- 50MHz master input clock:
      -- Baud Increment
      -- 115200 2416
      -- 38400 805
      -- 19200 403
      -- 9600 201
      -- 4800 101
      -- 2400 50

      serialClkCount <= serialClkCount + 2416;
    end if;
  end process;

end;

Credits

mit41301
8 projects • 9 followers
Hardware Engineer
Thanks to Aaron Brady.

Comments