Files
ZPU/cpu/zpu_uart_debug.vhd
2020-01-14 20:54:12 +00:00

517 lines
26 KiB
VHDL

---------------------------------------------------------------------------------------------------------
--
-- Name: zpu_uart_debug.vhd
-- Created: January 2019
-- Author(s): Philip Smart
-- Description: An extension of the simplistic UART Tx, still fixed at 8N1 with configurable baud rate
-- but adding a debug serialisaztion FSM for output of ZPU runtime data.
-- Credits: Originally using the simplistic UART as a guide, which was written by the following
-- authors:-
-- Philippe Carton, philippe.carton2 libertysurf.fr
-- Juan Pablo Daniel Borgna, jpdborgna gmail.com
-- Salvador E. Tropea, salvador inti.gob.ar
-- Copyright: (c) 2019 Philip Smart <philip.smart@net2net.org>
--
-- History: January 2019 - Initial module written using the simplistic UART as a guide but
-- adding cache and debug serialisation FSM.
--
---------------------------------------------------------------------------------------------------------
-- This source file is free software: you can redistribute it and-or modify
-- it under the terms of the GNU General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This source file is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http:--www.gnu.org-licenses->.
---------------------------------------------------------------------------------------------------------
library ieee;
library pkgs;
library work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.zpu_pkg.all;
-- Based on the simplistic UART, handles 8N1 RS232 Rx/Tx with independent programmable baud rate and selectable FIFO buffers.
entity zpu_uart_debug is
generic (
TX_FIFO_BIT_DEPTH : integer := DEBUG_MAX_TX_FIFO_BITS;
DBG_FIFO_BIT_DEPTH : integer := DEBUG_MAX_FIFO_BITS;
CLK_FREQ : integer := 100000000;
TX_BAUD_RATE : integer := DEBUG_TX_BAUD_RATE -- Default baud rate
);
port (
-- CPU Interface
CLK : in std_logic; -- memory master clock
RESET : in std_logic; -- high active sync reset
DEBUG_DATA : zpu_dbg_t;
CS : in std_logic; -- Chip Select.
READY : out std_logic; -- Debug processor ready to process new command.
-- Serial data
TXD : out std_logic
);
end zpu_uart_debug;
architecture rtl of zpu_uart_debug is
type DebugStates is
(
ST_IDLE,
ST_START,
ST_ADD_SEPERATOR,
ST_ADD_SPACE,
ST_PRECR,
ST_WRITE,
ST_WRITEHEX,
ST_WRITEBIN,
ST_SPLITSPACE,
ST_POSTSPACE,
ST_POSTCRLF,
ST_POSTLF,
ST_END
);
signal SM_STATE : DebugStates;
signal SM_BITCNT : integer range 0 to 7;
signal SM_BYTECNT : integer;
signal SM_WORDCNT : integer range 0 to 4;
signal SM_NIBBLECNT : std_logic;
signal SM_ADD_SEPERATOR : std_logic;
signal SM_ADD_SPACE : std_logic;
signal SM_SPLIT_DATA : std_logic_vector(1 downto 0);
type TXSTATES is (idle, bits);
signal TX_STATE : TXSTATES := idle;
signal TX_BUFFER : std_logic_vector(17 downto 0); -- Transmit serialisation buffer.
signal TX_DATA : std_logic_vector(7 downto 0); -- Transmit holding register.
signal TX_DATA_LOADED : std_logic; -- Data loaded into transmit buffer.
signal TX_COUNTER : unsigned(15 downto 0); -- TX Clock generator counter.
signal TX_CLOCK : std_logic; -- TX Clock.
signal TX_LOAD : std_logic; -- Load byte into TX fifo.
signal TX_FIFO_FULL : std_logic; -- TX Fifo is full = 1.
signal TX_WRITE_DATA : std_logic_vector(7 downto 0); -- write data
signal SM_DATA_IN : std_logic_vector(63 downto 0); -- Buffered input data.
signal DBGREC : zpu_dbg_t;
type ZPU_DBG_MEM_T is array (natural range 0 to ((2**DBG_FIFO_BIT_DEPTH)-1)) of zpu_dbg_t;
attribute ramstyle : string;
signal DBG_FIFO : ZPU_DBG_MEM_T;
attribute ramstyle of DBG_FIFO: signal is "M9K";
signal DBG_FIFO_WR_ADDR : unsigned(DBG_FIFO_BIT_DEPTH-1 downto 0);
signal DBG_FIFO_RD_ADDR : unsigned(DBG_FIFO_BIT_DEPTH-1 downto 0);
-- FIFO buffers.
--type TX_MEM_T is array (natural range 0 to ((2**TX_FIFO_BIT_DEPTH)-1)) of std_logic_vector(7 downto 0);
type TX_MEM_T is array (natural range 0 to ((2**TX_FIFO_BIT_DEPTH)-1)) of std_logic_vector(7 downto 0);
signal TX_FIFO : TX_MEM_T;
-- TX Fifo address pointers.
--signal TX_FIFO_WR_ADDR : unsigned(TX_FIFO_BIT_DEPTH-1 downto 0);
--signal TX_FIFO_RD_ADDR : unsigned(TX_FIFO_BIT_DEPTH-1 downto 0);
signal TX_FIFO_WR_ADDR : unsigned(TX_FIFO_BIT_DEPTH-1 downto 0);
signal TX_FIFO_RD_ADDR : unsigned(TX_FIFO_BIT_DEPTH-1 downto 0);
begin
-- Debug processor. External input provides a 32bit input which is translated to [1-4]x8bit characters
-- or [1-4]byte Hex roeds and sent to the debug uart transmitter.
--
process(CLK, RESET, DBG_FIFO_RD_ADDR, DBG_FIFO_WR_ADDR)
variable DBG_FULL_V : std_logic;
variable DBG_EMPTY_V : std_logic;
begin
if DBG_FIFO_RD_ADDR = DBG_FIFO_WR_ADDR then
DBG_EMPTY_V := '1';
else
DBG_EMPTY_V := '0';
end if;
if DBG_FIFO_WR_ADDR = DBG_FIFO_RD_ADDR-1 then
DBG_FULL_V := '1';
else
DBG_FULL_V := '0';
end if;
-- If we are to fill the last fifo slot, set ready to false so that no more writes occur (if cpu checking).
--
if (DBG_FIFO_WR_ADDR - DBG_FIFO_RD_ADDR) > ((2**DBG_FIFO_BIT_DEPTH)-2) then
READY <= '0';
elsif (DBG_FIFO_WR_ADDR - DBG_FIFO_RD_ADDR) < ((2**DBG_FIFO_BIT_DEPTH)-2) then
READY <= '1';
else
READY <= '0';
end if;
if RESET='1' then
TX_LOAD <= '0';
SM_STATE <= ST_IDLE;
SM_BYTECNT <= 0;
SM_WORDCNT <= 0;
SM_NIBBLECNT <= '1';
SM_ADD_SPACE <= '0';
SM_ADD_SEPERATOR <= '0';
DBG_FIFO_WR_ADDR <= (others => '0');
DBG_FIFO_RD_ADDR <= (others => '0');
READY <= '1';
elsif rising_edge(CLK) then
-- If cpu is writing a record to be processed, store in fifo if not full, otherwise wait.
--
if CS = '1' then
-- Store data in FIFO if not full.
--
if DBG_FULL_V = '0' then
DBG_FIFO(to_integer(DBG_FIFO_WR_ADDR)) <= DEBUG_DATA;
DBG_FIFO_WR_ADDR <= DBG_FIFO_WR_ADDR + 1;
end if;
end if;
-- When idle, if we have a record in the fifo, extract top record and process.
--
if SM_STATE = ST_IDLE and DBG_EMPTY_V = '0' then
DBGREC <= DBG_FIFO(to_integer(DBG_FIFO_RD_ADDR));
DBG_FIFO_RD_ADDR <= DBG_FIFO_RD_ADDR + 1;
SM_STATE <= ST_START;
SM_SPLIT_DATA <= "00";
end if;
-- Only add characters if the TX Fifo has space, otherwise suspend.
--
TX_LOAD <= '0';
if TX_FIFO_FULL = '0' then
case SM_STATE is
when ST_IDLE =>
when ST_START =>
if SM_ADD_SEPERATOR = '1' then
SM_STATE <= ST_ADD_SEPERATOR;
SM_ADD_SEPERATOR <= '0';
elsif SM_ADD_SPACE = '1' then
SM_STATE <= ST_ADD_SPACE;
SM_ADD_SPACE <= '0';
elsif DBGREC.FMT_PRE_SPACE = '1' then
SM_STATE <= ST_ADD_SPACE;
DBGREC.FMT_PRE_SPACE <= '0';
elsif DBGREC.FMT_PRE_CR = '1' then
SM_STATE <= ST_PRECR;
DBGREC.FMT_PRE_CR <= '0';
elsif DBGREC.WRITE_PC = '1' then
DBGREC.WRITE_PC <= '0';
SM_BYTECNT <= 4;
SM_DATA_IN(63 downto 32) <= std_logic_vector(to_unsigned(to_integer(unsigned(DBGREC.PC)), 32));
SM_ADD_SPACE <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.WRITE_SP = '1' then
DBGREC.WRITE_SP <= '0';
SM_BYTECNT <= 4;
SM_DATA_IN(63 downto 32) <= std_logic_vector(to_unsigned(to_integer(unsigned(DBGREC.SP)), 30)) & "00";
SM_ADD_SPACE <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.WRITE_STACK_TOS = '1' then
DBGREC.WRITE_STACK_TOS <= '0';
SM_BYTECNT <= 4;
SM_DATA_IN(63 downto 32) <= DBGREC.STACK_TOS;
SM_ADD_SPACE <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.WRITE_STACK_NOS = '1' then
DBGREC.WRITE_STACK_NOS <= '0';
SM_BYTECNT <= 4;
SM_DATA_IN(63 downto 32) <= DBGREC.STACK_NOS;
SM_ADD_SPACE <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.WRITE_OPCODE = '1' then
DBGREC.WRITE_OPCODE <= '0';
SM_BYTECNT <= 1;
SM_DATA_IN(63 downto 56) <= DBGREC.OPCODE;
SM_ADD_SEPERATOR <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.WRITE_DECODED_OPCODE = '1' then
DBGREC.WRITE_DECODED_OPCODE <= '0';
SM_BYTECNT <= 1;
SM_DATA_IN(63 downto 56) <= "00" & DBGREC.DECODED_OPCODE;
SM_ADD_SPACE <= '1';
SM_STATE <= ST_WRITEHEX;
elsif DBGREC.FMT_SPLIT_DATA /= "00" then
SM_SPLIT_DATA <= DBGREC.FMT_SPLIT_DATA;
DBGREC.FMT_SPLIT_DATA <= "00";
elsif DBGREC.WRITE_DATA = '1' then
DBGREC.WRITE_DATA <= '0';
SM_BITCNT <= 7;
SM_BYTECNT <= to_integer(unsigned(DBGREC.DATA_BYTECNT)) + 1;
SM_WORDCNT <= 0;
SM_DATA_IN <= DBGREC.DATA;
if DBGREC.FMT_DATA_PRTMODE = "10" then
SM_STATE <= ST_WRITEBIN;
elsif DBGREC.FMT_DATA_PRTMODE = "01" then
SM_STATE <= ST_WRITEHEX;
else
SM_STATE <= ST_WRITE;
end if;
elsif DBGREC.WRITE_DATA2 = '1' then
DBGREC.WRITE_DATA2 <= '0';
SM_BYTECNT <= to_integer(unsigned(DBGREC.DATA2_BYTECNT)) + 1;
SM_BITCNT <= 7;
SM_WORDCNT <= 0;
SM_DATA_IN <= DBGREC.DATA2;
if DBGREC.FMT_DATA_PRTMODE = "10" then
SM_STATE <= ST_WRITEBIN;
elsif DBGREC.FMT_DATA_PRTMODE = "01" then
SM_STATE <= ST_WRITEHEX;
else
SM_STATE <= ST_WRITE;
end if;
elsif DBGREC.WRITE_DATA3 = '1' then
DBGREC.WRITE_DATA3 <= '0';
SM_BYTECNT <= to_integer(unsigned(DBGREC.DATA3_BYTECNT)) + 1;
SM_BITCNT <= 7;
SM_WORDCNT <= 0;
SM_DATA_IN <= DBGREC.DATA3;
if DBGREC.FMT_DATA_PRTMODE = "10" then
SM_STATE <= ST_WRITEBIN;
elsif DBGREC.FMT_DATA_PRTMODE = "01" then
SM_STATE <= ST_WRITEHEX;
else
SM_STATE <= ST_WRITE;
end if;
elsif DBGREC.WRITE_DATA4 = '1' then
DBGREC.WRITE_DATA4 <= '0';
SM_BYTECNT <= to_integer(unsigned(DBGREC.DATA4_BYTECNT)) + 1;
SM_BITCNT <= 7;
SM_WORDCNT <= 0;
SM_DATA_IN <= DBGREC.DATA4;
if DBGREC.FMT_DATA_PRTMODE = "10" then
SM_STATE <= ST_WRITEBIN;
elsif DBGREC.FMT_DATA_PRTMODE = "01" then
SM_STATE <= ST_WRITEHEX;
else
SM_STATE <= ST_WRITE;
end if;
else
SM_STATE <= ST_END;
end if;
when ST_ADD_SPACE =>
TX_WRITE_DATA(7 downto 0) <= X"20";
TX_LOAD <= '1';
SM_STATE <= ST_START;
when ST_ADD_SEPERATOR =>
TX_WRITE_DATA(7 downto 0) <= X"2E";
TX_LOAD <= '1';
SM_STATE <= ST_START;
when ST_PRECR =>
TX_WRITE_DATA(7 downto 0) <= X"0D";
TX_LOAD <= '1';
SM_STATE <= ST_START;
when ST_WRITE =>
if SM_BYTECNT > 0 then
TX_WRITE_DATA(7 downto 0) <= SM_DATA_IN(63 downto 56);
TX_LOAD <= '1';
SM_DATA_IN(63 downto 8) <= SM_DATA_IN(55 downto 0);
SM_BYTECNT <= SM_BYTECNT - 1;
else
SM_STATE <= ST_START;
end if;
when ST_WRITEHEX =>
if SM_BYTECNT > 0 then
if unsigned(SM_DATA_IN(63 downto 60)) < 10 then
TX_WRITE_DATA(7 downto 0) <= std_logic_vector(unsigned(SM_DATA_IN(63 downto 60)) + X"30");
else
TX_WRITE_DATA(7 downto 0) <= std_logic_vector(unsigned(SM_DATA_IN(63 downto 60)) + X"57");
end if;
TX_LOAD <= '1';
SM_DATA_IN(63 downto 4) <= SM_DATA_IN(59 downto 0);
if SM_NIBBLECNT = '0' then
SM_BYTECNT <= SM_BYTECNT - 1;
if SM_SPLIT_DATA = "01" and SM_WORDCNT = 0 then
SM_STATE <= ST_SPLITSPACE;
SM_WORDCNT <= 0;
elsif SM_SPLIT_DATA = "10" and SM_WORDCNT = 1 then
SM_STATE <= ST_SPLITSPACE;
SM_WORDCNT <= 0;
elsif SM_SPLIT_DATA = "11" and SM_WORDCNT > 2 then
SM_STATE <= ST_SPLITSPACE;
SM_WORDCNT <= 0;
else
SM_WORDCNT <= SM_WORDCNT + 1;
end if;
end if;
SM_NIBBLECNT <= not SM_NIBBLECNT;
else
SM_STATE <= ST_START;
end if;
when ST_WRITEBIN =>
if SM_BYTECNT > 0 then
if SM_DATA_IN(63) = '1' then
TX_WRITE_DATA(7 downto 0) <= X"31";
else
TX_WRITE_DATA(7 downto 0) <= X"30";
end if;
TX_LOAD <= '1';
SM_DATA_IN(63 downto 1) <= SM_DATA_IN(62 downto 0);
if SM_BITCNT > 0 then
SM_BITCNT <= SM_BITCNT-1;
else
SM_BITCNT <= 7;
SM_BYTECNT <= SM_BYTECNT - 1;
end if;
else
SM_STATE <= ST_START;
end if;
when ST_SPLITSPACE =>
TX_WRITE_DATA(7 downto 0) <= X"20";
TX_LOAD <= '1';
SM_STATE <= ST_WRITEHEX;
when ST_POSTSPACE =>
TX_WRITE_DATA(7 downto 0) <= X"20";
TX_LOAD <= '1';
SM_STATE <= ST_END;
when ST_POSTCRLF =>
TX_WRITE_DATA(7 downto 0) <= X"0D";
TX_LOAD <= '1';
SM_STATE <= ST_POSTLF;
when ST_POSTLF =>
TX_WRITE_DATA(7 downto 0) <= X"0A";
TX_LOAD <= '1';
SM_STATE <= ST_END;
when ST_END =>
if DBGREC.FMT_POST_SPACE = '1' then
SM_STATE <= ST_POSTSPACE;
DBGREC.FMT_POST_SPACE <= '0';
elsif DBGREC.FMT_POST_CRLF = '1' then
SM_STATE <= ST_POSTCRLF;
DBGREC.FMT_POST_CRLF <= '0';
else
SM_STATE <= ST_IDLE;
end if;
end case;
end if;
end if;
end process;
-- Tx Clock generation
-- Very simple - the counter is reset when either it reaches zero or
-- the Tx is idle, and counts down once per system clock tick.
process(CLK, RESET)
begin
if RESET='1' then
TX_CLOCK <= '0';
TX_COUNTER <= to_unsigned(CLK_FREQ/TX_BAUD_RATE, TX_COUNTER'length);
elsif rising_edge(CLK) then
TX_CLOCK <= '0';
if TX_STATE = idle then
TX_COUNTER <= to_unsigned(CLK_FREQ/TX_BAUD_RATE, TX_COUNTER'length);
else
TX_COUNTER <= TX_COUNTER-1;
if TX_COUNTER = 0 then
TX_CLOCK <= '1';
TX_COUNTER <= to_unsigned(CLK_FREQ/TX_BAUD_RATE, TX_COUNTER'length);
end if;
end if;
end if;
end process;
-- Data Tx
-- Similarly to the Rx routine, we use a shift register larger than the word,
-- which also includes a marker bit. This time the marker bit is a zero, and when
-- the zero reaches bit 8, we know we've transmitted the entire word plus one stop bit.
process(clk,RESET,TX_STATE,TX_FIFO_RD_ADDR,TX_FIFO_WR_ADDR)
variable TX_FULL_V : std_logic;
variable TX_EMPTY_V : std_logic;
variable DATA_HEX_LSB : std_logic_vector(7 downto 0);
variable DATA_HEX_MSB : std_logic_vector(7 downto 0);
begin
if TX_FIFO_RD_ADDR=TX_FIFO_WR_ADDR then
TX_EMPTY_V := '1';
else
TX_EMPTY_V := '0';
end if;
if TX_FIFO_WR_ADDR = TX_FIFO_RD_ADDR-1 then
TX_FULL_V := '1';
else
TX_FULL_V := '0';
end if;
-- Full is set when we are almost full or an unspecified state and reset when the data in the buffer is 256 bytes less than
-- maximum.
if (TX_FIFO_WR_ADDR - TX_FIFO_RD_ADDR) > ((2**TX_FIFO_BIT_DEPTH)-16) then
TX_FIFO_FULL <= '1';
elsif (TX_FIFO_WR_ADDR - TX_FIFO_RD_ADDR) < ((2**TX_FIFO_BIT_DEPTH)-256) then
TX_FIFO_FULL <= '0';
else
TX_FIFO_FULL <= '1';
end if;
if RESET='1' then
TX_STATE <= idle;
TX_DATA_LOADED <= '0';
TXD <= '1';
TX_FIFO_FULL <= '0';
TX_FIFO_WR_ADDR <= (others => '0');
TX_FIFO_RD_ADDR <= (others => '0');
elsif rising_edge(clk) then
-- If CPU writes data, load into FIFO.
--
if TX_LOAD = '1' then
-- Store data in FIFO if not full.
--
if TX_FULL_V = '0' then
TX_FIFO(to_integer(TX_FIFO_WR_ADDR)) <= TX_WRITE_DATA(7 downto 0);
TX_FIFO_WR_ADDR <= TX_FIFO_WR_ADDR + 1;
end if;
end if;
-- If FIFO enabled, pop the next byte into the TX holding register.
if TX_DATA_LOADED = '0' and TX_EMPTY_V = '0' then
TX_DATA <= TX_FIFO(to_integer(TX_FIFO_RD_ADDR));
TX_FIFO_RD_ADDR <= TX_FIFO_RD_ADDR + 1;
TX_DATA_LOADED <= '1';
end if;
-- TX state machine, serialise the TX buffer.
case TX_STATE is
when idle =>
-- If data loaded into the TX holding register and we are at idle (ie last byte transmitted),
-- load into the transmit buffer and commence transmission.
--
if TX_DATA_LOADED = '1' then
TX_BUFFER <= "0111111111" & TX_DATA; -- marker bit + data
TX_STATE <= bits;
TXD <= '0'; -- Start bit
TX_DATA_LOADED <= '0';
end if;
when bits =>
if TX_CLOCK='1' then
TXD <= TX_BUFFER(0);
TX_BUFFER <= '0' & TX_BUFFER(17 downto 1);
if TX_BUFFER(8) = '0' then -- Marker bit has reached bit 8
TX_STATE <= idle;
end if;
end if;
when others =>
TX_STATE <= idle;
end case;
end if;
end process;
end architecture;