T80 CPU Sim - red-bote/VHDL_Demos GitHub Wiki
T80 VHDL CPU IP Core
On Open Cores (requires registration).
Equivalent to Open Cores Version : 0247
From Pac-Man VHDL original sources ... pacman_rel004_sp3e/t80_latest/T80_v301.zip ... T80/t80_original_latest/t80.tar.gz
Version 0350 standalone project repo
Version 351 development for the MIST FPGA board
FreeCores is a fork of almost all cores that was once on OpenCores.org
Pretty good reference for Z80 on homepage of Watis Leelapatra Khon Kaen University.
Minimal T80 Circuit
The VHDL project implements a minimal test-bench for experimentation with the T80 core.
Import T80 files from trunk/rtl/vhdl/:
- T80se.vhd (synchronous version with clock-enable)
- T80.vhd
- T80_MCode.vhd
- T80_ALU.vhd
- T80_Reg.vhd (not extended register set)
- T80_Pack.vhd (T80 package library)
Instantiate T80 with minimal necessary signal connections:
architecture Behavioral of rtl_top is
signal reset_l : std_logic;
signal clk_cpu : std_logic;
signal clk_cnt : std_logic_vector (3 downto 0);
signal cpu_addr : std_logic_vector (15 downto 0);
signal cpu_data_in : std_logic_vector (7 downto 0);
signal cpu_data_out : std_logic_vector (7 downto 0);
begin
u_clk : entity work.clock_div
port map (
C => clk,
CLR => reset,
Q => clk_cnt
);
clk_cpu <= clk_cnt(3);
reset_l <= not reset;
u_cpu : entity work.T80s
port map(
RESET_n => reset_l,
CLK_n => clk_cpu,
WAIT_n => '1', -- cpu_wait_l,
INT_n => '1', -- cpu_int_l,
NMI_n => '1', -- cpu_nmi_l,
BUSRQ_n => '1', -- cpu_busrq_l,
M1_n => open, -- cpu_m1_l,
MREQ_n => open, -- cpu_mreq_l,
IORQ_n => open, -- cpu_iorq_l,
RD_n => open, -- cpu_rd_l,
WR_n => open, -- cpu_wr_l,
A => cpu_addr,
DI => (others => '1'), -- cpu_data_in,
DO => cpu_data_out
);
end Behavioral;
With only a clock source connected, the Z80 state can be evaluated for some minimal functionality.
- At init: PC=0, SP=$FFFF
- DI tied to $FF
- @ M1:=0, read opcode $FF (RST $38) from DI and PC+=1
- RST $38: push PCl and PCh to stack
- SP decrement, $FFFE to address bus, PCh ($00) to DO
- SP decrement, $FFFD to address bus, PCl ($01) to DO
- DI tied to $FF
- @ next M1, Read $FF from DI and PC+=1 (opcode RST $38 again)
- $0038 to A, then read opcode $FF to DI
- PC:=$0038+1=$0039, $0039 pushed to $FFFC and $FFFB
Program ROM
Create z80 test code and run assembler:
org 0
l_loop:
add a, 0x5A ;0100 c6 5a
jr l_loop ;0102 18 fc (jr $-3)
Disassembly in code comments from output of z80dasm -t a.bin
Convert binary to VHDL:
hex2rom -b a.bin prog_rom 6l8s > t80_vga/t80_vga.srcs/sources_1/new/prog_rom.vhd
Reset Behavioral Simulation in Vivado after update to prog_rom.vhd.
The T80 with program ROM (but no work RAM), enables the data bus from the ROM to the CPU using only the RD_n
signal from T80.
u_prog_rom : entity work.prog_rom
port map (
Clk => clk_cpu, -- can 100 Mhz system clock be used here?
A => cpu_addr(5 downto 0),
D => rom_data_out
);
cpu_data_in <= rom_data_out when cpu_rd_l = '0' else (others => '1');
u_cpu : entity work.T80se
port map(
RESET_n => reset_l,
CLK_n => clk_cpu, -- system clock?
CLKEN => '1', -- clock enabled for CPU frequency?
WAIT_n => '1',
INT_n => '1',
NMI_n => '1',
BUSRQ_n => '1',
M1_n => open,
MREQ_n => open,
IORQ_n => open,
RD_n => cpu_rd_l,
WR_n => open,
A => cpu_addr,
DI => cpu_data_in,
DO => open
);
- 4 bytes program code: $C6 $5A $18 $FC
- address bus shows op-code bytes fetched from $0000, $0001, $0002, $0003
- address bus also shows refresh accesses R==$00, R==$01, R==$02
- Refresh reads are distinguished from normal memory reads because
RD_n
is not active
Unlike a real Z80, the T80 has separate In and Out data bus. Although the program has no operation to write data out, the $5A
of the ld A, 0x5A
appears on DO bus (have not confirmed if this mimics a real Z80 behavior).
Adding Work RAM
A single-port RAM with enable is added with additional VHDL code to provide an extremely simple example of RAM and ROM chip selection:
ram_cs <= cpu_addr(15);
mem_rd <= not(cpu_rd_l or cpu_mreq_l);
process(ram_cs, mem_rd)
begin
cpu_data_in <= (others => '1');
if mem_rd = '1' then
if ram_cs = '1' then
cpu_data_in <= ram_data_out(7 downto 0);
else
cpu_data_in <= rom_data_out;
end if;
end if;
end process;
ram_wr_en <= not cpu_wr_l;
ram_data_in <= x"00" & cpu_data_out;
u_work_ram : entity work.rams_08
port map (
clk => clk, -- system clock
en => ram_cs,
we => ram_wr_en,
a => cpu_addr(5 downto 0),
di => ram_data_in,
do => ram_data_out
);
Z80 code to test writing to RAM:
org 0
ld hl, l_ram_start ;0100 21 00 80
l_loop:
inc a ;0103 3c
ld (hl), a ;0104 77
inc l ;0105 2c
jr l_loop ;0106 18 fb (jr $-3)
org 0x8000
l_ram_start:
db 0x00
In the simulation trace the opcodes are read from ROM and appear on the DI bus. Address $8000 is accessed to write data to RAM. The RAM is shown with the first few RAM locations written. There is no read from RAM.
The following simple test writes the starting value $5A to RAM[0] and then increments it in a loop.
org 0
l_0:
ld hl, l_ram_start ;0100 21 00 80
ld a, 0x5A ;0103 3e 5a
ld (hl), a ;0105 77
l_loop:
inc (hl) ;0106 34
jr l_loop ;0107 18 fd (jr $-1)
org 0x8000
l_ram_start:
db 0x00
- Data $5A is first seen on DI with
ld a, $5A
- Data $5A is seen on DO with
ld (hl), a
inc (hl)
is shorthand for reading data from address pointed to by HL, increment and write back to same address in HL.- Data $5A is seen on DI, then incremented value $5B is seen on DO written back to RAM.
Test trace exhibits that inc (hl)
is more or less equivalent to:
ld a, (hl)
inc a
ld (hl), a