AtomBusMon/src/Z80CpuMon.vhd

673 lines
23 KiB
VHDL

--------------------------------------------------------------------------------
-- Copyright (c) 2019 David Banks
--
--------------------------------------------------------------------------------
-- ____ ____
-- / /\/ /
-- /___/ \ /
-- \ \ \/
-- \ \
-- / / Filename : Z80CpuMon.vhd
-- /___/ /\ Timestamp : 14/10/2018
-- \ \ / \
-- \___\/\___\
--
--Design Name: Z80CpuMon
--Device: multiple
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
entity Z80CpuMon is
generic (
ClkMult : integer;
ClkDiv : integer;
ClkPer : real;
num_comparators : integer;
avr_prog_mem_size : integer
);
port (
clock : in std_logic;
-- Z80 Signals
RESET_n : in std_logic;
CLK_n : in std_logic;
WAIT_n : in std_logic;
INT_n : in std_logic;
NMI_n : in std_logic;
BUSRQ_n : in std_logic;
M1_n : out std_logic;
MREQ_n : out std_logic;
IORQ_n : out std_logic;
RD_n : out std_logic;
WR_n : out std_logic;
RFSH_n : out std_logic;
HALT_n : out std_logic;
BUSAK_n : out std_logic;
Addr : out std_logic_vector(15 downto 0);
Data : inout std_logic_vector(7 downto 0);
-- Buffer Control Signals
DIRD : out std_logic;
tristate_n : out std_logic;
tristate_ad_n : out std_logic;
-- Mode jumper, tie low to generate NOPs when paused
mode : in std_logic;
-- External trigger inputs
trig : in std_logic_vector(1 downto 0);
-- Serial Console
avr_RxD : in std_logic;
avr_TxD : out std_logic;
-- Switches
sw_reset_cpu : in std_logic;
sw_reset_avr : in std_logic;
-- LEDs
led_bkpt : out std_logic;
led_trig0 : out std_logic;
led_trig1 : out std_logic;
-- OHO_DY1 connected to test connector
tmosi : out std_logic;
tdin : out std_logic;
tcclk : out std_logic;
-- Debugging signals
test1 : out std_logic;
test2 : out std_logic;
test3 : out std_logic;
test4 : out std_logic
);
end Z80CpuMon;
architecture behavioral of Z80CpuMon is
type state_type is (idle, nop_t1, nop_t2, nop_t3, nop_t4, rd_t1, rd_wa, rd_t2, rd_t3, wr_t1, wr_wa, wr_t2, wr_t3, busack);
signal state : state_type;
signal clock_avr : std_logic;
signal cpu_reset_n : std_logic;
signal cpu_clk : std_logic;
signal cpu_clken : std_logic;
signal busmon_clk : std_logic;
signal Addr_int : std_logic_vector(15 downto 0);
signal Addr1 : std_logic_vector(15 downto 0);
signal Addr2 : std_logic_vector(15 downto 0);
signal RD_n_int : std_logic;
signal WR_n_int : std_logic;
signal MREQ_n_int : std_logic;
signal IORQ_n_int : std_logic;
signal RFSH_n_int : std_logic;
signal M1_n_int : std_logic;
signal BUSAK_n_int : std_logic;
signal WAIT_n_latched : std_logic;
signal TState : std_logic_vector(2 downto 0);
signal SS_Single : std_logic;
signal SS_Step : std_logic;
signal SS_Step_held : std_logic;
signal CountCycle : std_logic;
signal int_ctrl : std_logic_vector(7 downto 0);
signal skipNextOpcode : std_logic;
signal Regs : std_logic_vector(255 downto 0);
signal PdcData : std_logic_vector(7 downto 0);
signal io_not_mem : std_logic;
signal io_rd : std_logic;
signal io_wr : std_logic;
signal memory_rd : std_logic;
signal memory_wr : std_logic;
signal memory_addr : std_logic_vector(15 downto 0);
signal memory_dout : std_logic_vector(7 downto 0);
signal memory_din : std_logic_vector(7 downto 0);
signal memory_done : std_logic;
signal io_rd1 : std_logic;
signal io_wr1 : std_logic;
signal memory_rd1 : std_logic;
signal memory_wr1 : std_logic;
signal mon_m1_n : std_logic;
signal mon_xx_n : std_logic; -- shorten MREQ and RD in M1 NOP cycle
signal mon_yy : std_logic; -- delay IORQ/RD/WR in IO cycle
signal mon_mreq_n : std_logic;
signal mon_iorq_n : std_logic;
signal mon_rfsh_n : std_logic;
signal mon_rd_n : std_logic;
signal mon_wr_n : std_logic;
signal mon_busak_n1 : std_logic;
signal mon_busak_n2 : std_logic;
signal mon_busak_n : std_logic;
signal BUSRQ_n_sync : std_logic;
signal INT_n_sync : std_logic;
signal NMI_n_sync : std_logic;
signal RESET_n_sync : std_logic;
signal Read_n : std_logic;
signal Read_n0 : std_logic;
signal Write_n : std_logic;
signal Write_n0 : std_logic;
signal ReadIO_n : std_logic;
signal ReadIO_n0 : std_logic;
signal WriteIO_n : std_logic;
signal WriteIO_n0 : std_logic;
signal Sync : std_logic;
signal Sync0 : std_logic;
signal Sync1 : std_logic;
signal Din : std_logic_vector(7 downto 0);
signal Dout : std_logic_vector(7 downto 0);
signal Den : std_logic;
signal ex_data : std_logic_vector(7 downto 0);
signal rd_data : std_logic_vector(7 downto 0);
signal wr_data : std_logic_vector(7 downto 0);
signal mon_data : std_logic_vector(7 downto 0);
signal avr_TxD_int : std_logic;
signal rfsh_addr : std_logic_vector(15 downto 0);
begin
--------------------------------------------------------
-- Clocking
--------------------------------------------------------
inst_dcm0 : entity work.DCM0
generic map (
ClkMult => ClkMult,
ClkDiv => ClkDiv,
ClkPer => ClkPer
)
port map(
CLKIN_IN => clock,
CLKFX_OUT => clock_avr
);
cpu_clk <= CLK_n;
busmon_clk <= CLK_n;
--------------------------------------------------------
-- BusMonCore
--------------------------------------------------------
mon : entity work.BusMonCore
generic map (
num_comparators => num_comparators,
avr_prog_mem_size => avr_prog_mem_size
)
port map (
clock_avr => clock_avr,
busmon_clk => busmon_clk,
busmon_clken => '1',
cpu_clk => cpu_clk,
cpu_clken => '1',
Addr => Addr_int,
Data => mon_data,
Rd_n => Read_n,
Wr_n => Write_n,
RdIO_n => ReadIO_n,
WrIO_n => WriteIO_n,
Sync => Sync,
Rdy => open,
nRSTin => RESET_n_sync,
nRSTout => cpu_reset_n,
CountCycle => CountCycle,
trig => trig,
avr_RxD => avr_RxD,
avr_TxD => avr_TxD_int,
sw_reset_cpu => sw_reset_cpu,
sw_reset_avr => sw_reset_avr,
led_bkpt => led_bkpt,
led_trig0 => led_trig0,
led_trig1 => led_trig1,
tmosi => tmosi,
tdin => tdin,
tcclk => tcclk,
Regs => Regs,
PdcData => PdcData,
RdMemOut => memory_rd,
WrMemOut => memory_wr,
RdIOOut => io_rd,
WrIOOut => io_wr,
AddrOut => memory_addr,
DataOut => memory_dout,
DataIn => memory_din,
Done => memory_done,
int_ctrl => int_ctrl,
SS_Single => SS_Single,
SS_Step => SS_Step
);
--------------------------------------------------------
-- T80
--------------------------------------------------------
inst_t80: entity work.T80a port map (
TS => TState,
Regs => Regs,
PdcData => PdcData,
RESET_n => cpu_reset_n,
CLK_n => cpu_clk,
CEN => cpu_clken,
WAIT_n => WAIT_n,
INT_n => INT_n_sync,
NMI_n => NMI_n_sync,
BUSRQ_n => BUSRQ_n,
M1_n => M1_n_int,
MREQ_n => MREQ_n_int,
IORQ_n => IORQ_n_int,
RD_n => RD_n_int,
WR_n => WR_n_int,
RFSH_n => RFSH_n_int,
HALT_n => HALT_n,
BUSAK_n => BUSAK_n_int,
A => Addr_int,
Din => Din,
Dout => Dout,
DEn => Den
);
--------------------------------------------------------
-- Synchronise external interrupts
--------------------------------------------------------
int_gen : process(CLK_n)
begin
if rising_edge(CLK_n) then
if int_ctrl(1) = '1' then
BUSRQ_n_sync <= int_ctrl(0);
else
BUSRQ_n_sync <= BUSRQ_n or (int_ctrl(0) and SS_single);
end if;
if int_ctrl(3) = '1' then
INT_n_sync <= int_ctrl(2);
else
INT_n_sync <= INT_n or (int_ctrl(2) and SS_single);
end if;
if int_ctrl(5) = '1' then
NMI_n_sync <= int_ctrl(4);
else
NMI_n_sync <= NMI_n or (int_ctrl(4) and SS_single);
end if;
if int_ctrl(7) = '1' then
RESET_n_sync <= int_ctrl(6);
else
RESET_n_sync <= RESET_n or (int_ctrl(6) and SS_single);
end if;
end if;
end process;
--------------------------------------------------------
-- Z80 specific single step / breakpoint logic
--------------------------------------------------------
CountCycle <= '1' when state = idle else '0';
-- The breakpoint logic stops the Z80 in M1/T3 using cpu_clken
cpu_clken <= '0' when state = idle and SS_Single = '1' and Sync1 = '1' else
'0' when state /= idle else
'1';
-- Logic to ignore the second M1 in multi-byte opcodes
skip_opcode_latch : process(CLK_n)
begin
if rising_edge(CLK_n) then
if (M1_n_int = '0' and WAIT_n_latched = '1' and TState = "010") then
if (skipNextOpcode = '0' and (Data = x"CB" or Data = x"DD" or Data = x"ED" or Data = x"FD")) then
skipNextOpcode <= '1';
else
skipNextOpcode <= '0';
end if;
end if;
end if;
end process;
-- For instruction breakpoints, we make the monitoring decision as early as possibe
-- to allow time to stop the current instruction, which is possible because we don't
-- really care about the data (it's re-read from memory by the disassembler).
Sync0 <= '1' when WAIT_n = '1' and M1_n_int = '0' and TState = "010" and skipNextOpcode = '0' else '0';
-- For reads/write breakpoints we make the monitoring decision in the middle of T3
Read_n0 <= not ((not RD_n_int) and (not MREQ_n_int) and (M1_n_int)) when TState = "011" else '1';
Write_n0 <= not (( RD_n_int) and (not MREQ_n_int) and (M1_n_int)) when TState = "011" else '1';
ReadIO_n0 <= not ((not RD_n_int) and (not IORQ_n_int) and (M1_n_int)) when TState = "011" else '1';
WriteIO_n0 <= not (( RD_n_int) and (not IORQ_n_int) and (M1_n_int)) when TState = "011" else '1';
-- Hold the monitoring decision so it is valid on the rising edge of the clock
-- For instruction fetches the monitor sees these at the end of T2
-- For reads and writes, the data is sampled in the middle of T3 so delay until end of T3
watch_gen : process(CLK_n)
begin
if falling_edge(CLK_n) then
Sync <= Sync0;
Read_n <= Read_n0;
Write_n <= Write_n0;
ReadIO_n <= ReadIO_n0;
WriteIO_n <= WriteIO_n0;
-- Latch wait seen by T80 on the falling edge, for use on the next rising edge
WAIT_n_latched <= WAIT_n;
end if;
end process;
-- Register the exec data on the rising edge of the clock at the end of T2
ex_data_latch : process(CLK_n)
begin
if rising_edge(CLK_n) then
if Sync = '1' then
ex_data <= Data;
end if;
end if;
end process;
-- Register the read data on the falling edge of clock in the middle of T3
rd_data_latch : process(CLK_n)
begin
if falling_edge(CLK_n) then
if Read_n0 = '0' or ReadIO_n0 = '0' then
rd_data <= Data;
end if;
memory_din <= Data;
end if;
end process;
-- Register the read data on the falling edge of clock in the middle of T3
wr_data_latch : process(CLK_n)
begin
if falling_edge(CLK_n) then
if Write_n0 = '0' or WriteIO_n0 = '0' then
wr_data <= Data;
end if;
end if;
end process;
-- Mux the data seen by the bus monitor appropriately
mon_data <= rd_data when Read_n = '0' or ReadIO_n = '0' else
wr_data when Write_n = '0' or WriteIO_n = '0' else
ex_data;
-- Mark the memory access as done when t3 is reached
memory_done <= '1' when state = rd_t3 or state = wr_t3 else '0';
-- Multiplex the bus control signals
-- The _int versions come from the T80
-- The mon_ versions come from the state machine below
MREQ_n <= MREQ_n_int when state = idle else mon_mreq_n and mon_xx_n;
IORQ_n <= IORQ_n_int when state = idle else (mon_iorq_n or mon_yy);
WR_n <= WR_n_int when state = idle else (mon_wr_n or mon_yy);
RD_n <= RD_n_int when state = idle else (mon_rd_n or mon_yy) and mon_xx_n;
RFSH_n <= RFSH_n_int when state = idle else mon_rfsh_n;
M1_n <= M1_n_int when state = idle else mon_m1_n;
Addr1 <= x"0000" when state = nop_t1 or state = nop_t2 else
rfsh_addr when state = nop_t3 or state = nop_t4 else
memory_addr when state /= idle else
Addr_int;
tristate_n <= BUSAK_n_int when state = idle else mon_busak_n1;
BUSAK_n <= BUSAK_n_int when state = idle else mon_busak_n;
-- Force the address and databus to tristate when reset is asserted
tristate_ad_n <= '0' when RESET_n_sync = '0' else
BUSAK_n_int when state = idle else
mon_busak_n1;
-- The Acorn Z80 Second Processor needs ~10ns of address hold time following M1
-- and MREQ being released at the start of T3. Otherwise, the ROM switching
-- during NMI doesn't work reliably due to glitches. See:
-- https://stardot.org.uk/forums/viewtopic.php?p=212096#p212096
--
-- Reordering the above Addr expression so Addr_int is last instead of
-- first seems to fix the issue, but is clearly very dependent on how the Xilinx
-- tools route the design.
--
-- If the problem recurs, we should switch to something like:
--
addr_delay : process(clock)
begin
if rising_edge(clock) then
Addr2 <= Addr1;
Addr <= Addr2;
end if;
end process;
Data <= memory_dout when (state = wr_t1 and io_not_mem = '1') or state = wr_wa or state = wr_t2 or state = wr_t3 else
Dout when state = idle and Den = '1' else
(others => 'Z');
DIRD <= '0' when (state = wr_t1 and io_not_mem = '1') or state = wr_wa or state = wr_t2 or state = wr_t3 else
'0' when state = idle and Den = '1' else
'1';
Din <= Data;
men_access_machine_rising : process(CLK_n, cpu_reset_n)
begin
if (cpu_reset_n = '0') then
state <= idle;
memory_rd1 <= '0';
memory_wr1 <= '0';
io_rd1 <= '0';
io_wr1 <= '0';
SS_Step_held <= '0';
mon_rfsh_n <= '1';
mon_m1_n <= '1';
mon_xx_n <= '1';
mon_yy <= '0';
mon_busak_n1 <= '1';
elsif rising_edge(CLK_n) then
-- Extend the 1-cycle long request strobes from BusMonCore
-- until we are ready to generate a bus cycle
if memory_rd = '1' then
memory_rd1 <= '1';
elsif state = rd_t1 then
memory_rd1 <= '0';
end if;
if memory_wr = '1' then
memory_wr1 <= '1';
elsif state = wr_t1 then
memory_wr1 <= '0';
end if;
if io_rd = '1' then
io_rd1 <= '1';
elsif state = rd_t1 then
io_rd1 <= '0';
end if;
if io_wr = '1' then
io_wr1 <= '1';
elsif state = wr_t1 then
io_wr1 <= '0';
end if;
if SS_Step = '1' then
SS_Step_held <= '1';
elsif state = idle then
SS_Step_held <= '0';
end if;
Sync1 <= Sync;
-- Main state machine, generating refresh, read and write cycles
-- (the timing should exactly match those of the Z80)
case state is
-- Idle is when T80 is running
when idle =>
if SS_Single = '1' and Sync1 = '1' then
-- Load the initial refresh address from I/R in the T80
rfsh_addr <= Regs(199 downto 192) & Regs(207 downto 200);
-- Start genering NOP cycles
mon_rfsh_n <= '0';
state <= nop_t3;
end if;
-- NOP cycle
when nop_t1 =>
state <= nop_t2;
-- Increment the refresh address (7 bits, just like the Z80)
rfsh_addr(6 downto 0) <= rfsh_addr(6 downto 0) + 1;
mon_xx_n <= mode;
when nop_t2 =>
if WAIT_n_latched = '1' then
mon_m1_n <= '1';
mon_xx_n <= '1';
if SS_Step_held = '1' or SS_Single = '0' then
state <= idle;
else
mon_rfsh_n <= '0';
state <= nop_t3;
end if;
end if;
when nop_t3 =>
state <= nop_t4;
when nop_t4 =>
mon_rfsh_n <= '1';
-- Sample BUSRQ_n at the *start* of the final T-state
-- (hence using BUSRQ_n_sync)
if BUSRQ_n_sync = '0' then
state <= busack;
mon_busak_n1 <= '0';
elsif memory_wr1 = '1' or io_wr1 = '1' then
state <= wr_t1;
io_not_mem <= io_wr1;
mon_yy <= io_wr1;
elsif memory_rd1 = '1' or io_rd1 = '1' then
state <= rd_t1;
io_not_mem <= io_rd1;
mon_yy <= io_rd1;
else
state <= nop_t1;
mon_m1_n <= mode;
end if;
-- Read cycle
when rd_t1 =>
mon_yy <= '0';
if io_not_mem = '1' then
state <= rd_wa;
else
state <= rd_t2;
end if;
when rd_wa =>
state <= rd_t2;
when rd_t2 =>
if WAIT_n_latched = '1' then
state <= rd_t3;
end if;
when rd_t3 =>
-- Sample BUSRQ_n at the *start* of the final T-state
-- (hence using BUSRQ_n_sync)
if BUSRQ_n_sync = '0' then
state <= busack;
mon_busak_n1 <= '0';
else
state <= nop_t1;
mon_m1_n <= mode;
end if;
-- Write cycle
when wr_t1 =>
mon_yy <= '0';
if io_not_mem = '1' then
state <= wr_wa;
else
state <= wr_t2;
end if;
when wr_wa =>
state <= wr_t2;
when wr_t2 =>
if WAIT_n_latched = '1' then
state <= wr_t3;
end if;
when wr_t3 =>
-- Sample BUSRQ_n at the *start* of the final T-state
-- (hence using BUSRQ_n_sync)
if BUSRQ_n_sync = '0' then
state <= busack;
mon_busak_n1 <= '0';
else
state <= nop_t1;
mon_m1_n <= mode;
end if;
-- Bus Request/Ack cycle
when busack =>
-- Release BUSAK_n on the next rising edge after BUSRQ_n seen
-- (hence using BUSRQ_n)
if BUSRQ_n_sync = '1' then
state <= nop_t1;
mon_m1_n <= mode;
mon_busak_n1 <= '1';
end if;
end case;
end if;
end process;
men_access_machine_falling : process(CLK_n)
begin
if falling_edge(CLK_n) then
-- For memory access cycles, mreq/iorq/rd/wr all change in the middle of
-- the t state, so retime these on the falling edge of clock
if state = rd_t1 or state = rd_wa or state = rd_t2 or state = wr_t1 or state = wr_wa or state = wr_t2 then
if io_not_mem = '0' then
-- Memory cycle
mon_mreq_n <= '0';
mon_iorq_n <= '1';
else
-- IO cycle
mon_mreq_n <= '1';
mon_iorq_n <= '0';
end if;
elsif (state = nop_t1 and mode = '0') or state = nop_t3 then
-- M1 cycle
mon_mreq_n <= '0';
mon_iorq_n <= '1';
else
-- Idle cycle
mon_mreq_n <= '1';
mon_iorq_n <= '1';
end if;
-- Read strobe
if (state = nop_t1 and mode = '0') or state = rd_t1 or state = rd_wa or state = rd_t2 then
mon_rd_n <= '0';
else
mon_rd_n <= '1';
end if;
-- Write strobe
if (state = wr_t1 and io_not_mem = '1') or state = wr_wa or state = wr_t2 then
mon_wr_n <= '0';
else
mon_wr_n <= '1';
end if;
-- Half-cycle delayed version of BUSRQ_n_sync
mon_busak_n2 <= BUSRQ_n_sync;
end if;
end process;
mon_busak_n <= mon_busak_n1 or mon_busak_n2;
avr_TxD <= avr_Txd_int;
test1 <= Sync1;
test2 <= TState(0);
test3 <= TState(1);
test4 <= TState(2);
end behavioral;