Magellan – a hardware monitor/debugger (II)

Part 2 – Registers monitoring

In this part we will integrate several blocks:

  1. AXI infrastructure, defined on a Vivado block design, including a JTAG to AXI master
  2. AXI-Lite registers block, with adaptations for this tutorial from the original one published on Code Snippets Seven segment driver for Basys 3 (presented in part 1 of this tutorial)
  3. Seven segment decoder for Basys 3 (presented in part 1 of this tutorial)
  4. An extension of the top block that was used in part 1, to instantiate the new blocks and add additional functionality.

You can see a full diagram of the project below:

In this project, we implement a bank of sixteen, 16-bits wide registers. The value of each of the registers can be monitored in two ways:

  • Entering the address of the register in binary by means of switches 0 to 3, we will see the value of the registers on the seven-segments display.
  • Sending TCL read commands on Vivado, via the JTAG AXI master (more on this later).

The value of any writeable register can be changed using TCL write commands.

AXI infrastructure (block design)

This project uses a Vivado block design to integrate one IP block (JTAG to AXI master), the interconnection, clocks, and reset.

The JTAG to AXI block takes commands (sent via TCL) and converts them to AXI transactions. The transactions are routed through the AXI interconnect to the axil_regs port (AXI4-Lite). The reset and clock signals for the design are also generated on the block design. We will describe the TCL commands for the JTAG to AXI block in another section below.

AXI Lite registers

This block is based on the AXI-Lite registers bank VHDL module presented on Code Snippets.

However, since the Basys 3 has a four digits seven segment display, the width of the registers was limited to 16-bit instead of the 32-bit size of the mentioned block.

The register bank implements two read-only registers and several read-write registers:

  1. Version register on address 0x0 (read-only)
  2. Date register on address 0x2 (read-only)

The block also has a monitor port, with addr_mon (4 bits) and data_mon (16 bits). The block will output on data_mon the value of register n, where n is the address represented on addr_mon (values from 0 to 15).

Seven segment decoder

This block is exactly the same seven-segment decoder as described on the Code Snippets.

Top

The VHDL code for the top block is presented below

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

entity top is
	
  port (
		reset     :  in std_logic;
		sys_clock :  in std_logic;
		
		-- inputs
		SW        :  in std_logic_vector(15 downto 0);
		
		-- outputs
		LED       : out std_logic_vector (15 downto 0);
        SSEG_CA   : out std_logic_vector (7 downto 0);
        SSEG_AN   : out std_logic_vector (3 downto 0)
	);
end top;

architecture rtl of top is
  component bd_wrapper is
    port (
      axil_regs_araddr  : out STD_LOGIC_VECTOR ( 31 downto 0 );
      axil_regs_arprot  : out STD_LOGIC_VECTOR ( 2 downto 0 );
      axil_regs_arready : in STD_LOGIC;
      axil_regs_arvalid : out STD_LOGIC;
      axil_regs_awaddr  : out STD_LOGIC_VECTOR ( 31 downto 0 );
      axil_regs_awprot  : out STD_LOGIC_VECTOR ( 2 downto 0 );
      axil_regs_awready : in STD_LOGIC;
      axil_regs_awvalid : out STD_LOGIC;
      axil_regs_bready  : out STD_LOGIC;
      axil_regs_bresp   : in STD_LOGIC_VECTOR ( 1 downto 0 );
      axil_regs_bvalid  : in STD_LOGIC;
      axil_regs_rdata   : in STD_LOGIC_VECTOR ( 31 downto 0 );
      axil_regs_rready  : out STD_LOGIC;
      axil_regs_rresp   : in STD_LOGIC_VECTOR ( 1 downto 0 );
      axil_regs_rvalid  : in STD_LOGIC;
      axil_regs_wdata   : out STD_LOGIC_VECTOR ( 31 downto 0 );
      axil_regs_wready  : in STD_LOGIC;
      axil_regs_wstrb   : out STD_LOGIC_VECTOR ( 3 downto 0 );
      axil_regs_wvalid  : out STD_LOGIC;
      clk               : out STD_LOGIC;
      reset             : in STD_LOGIC;
      rstn              : out STD_LOGIC_VECTOR ( 0 to 0 );
      sys_clock         : in STD_LOGIC
    );
  end component;

  component axil_regs_xil is
    generic (
      C_S_AXI_DATA_WIDTH	: integer	:= 16
    );
    port (
      s_axi_aclk	: in std_logic;
      s_axi_aresetn	: in std_logic;
      s_axi_awaddr	: in std_logic_vector(4 downto 0);
      s_axi_awvalid	: in std_logic;
      s_axi_awready	: out std_logic; 
      s_axi_wdata	: in std_logic_vector(c_s_axi_data_width-1 downto 0);
      s_axi_wvalid	: in std_logic;
      s_axi_wready	: out std_logic;
      s_axi_bresp	: out std_logic_vector(1 downto 0);
      s_axi_bvalid	: out std_logic;
      s_axi_bready	: in std_logic;
      s_axi_araddr	: in std_logic_vector(4 downto 0);
      s_axi_arvalid	: in std_logic;
      s_axi_arready	: out std_logic;
      s_axi_rdata	: out std_logic_vector(c_s_axi_data_width-1 downto 0);
      s_axi_rresp	: out std_logic_vector(1 downto 0);
      s_axi_rvalid	: out std_logic;
      s_axi_rready	: in std_logic;
    
      addr_mon      : in std_logic_vector(3 downto 0);
      data_mon      : out std_logic_vector(15 downto 0)
    );
  end component;

  component bin2_7seg is
     port (
        data_in:    in std_logic_vector (3 downto 0);
        data_out:   out std_logic_vector (6 downto 0)
     );
  end component;


  signal counter_reg : std_logic_vector (17 downto 0);
  signal clk         : std_logic;
  signal rstn        : std_logic;
  signal axil_regs_araddr  : STD_LOGIC_VECTOR ( 31 downto 0 );
  signal axil_regs_arready : STD_LOGIC;
  signal axil_regs_arvalid : STD_LOGIC;
  signal axil_regs_awaddr  : STD_LOGIC_VECTOR ( 31 downto 0 );
  signal axil_regs_awready : STD_LOGIC;
  signal axil_regs_awvalid : STD_LOGIC;
  signal axil_regs_bready  : STD_LOGIC;
  signal axil_regs_bresp   : STD_LOGIC_VECTOR ( 1 downto 0 );
  signal axil_regs_bvalid  : STD_LOGIC;
  signal axil_regs_rdata   : STD_LOGIC_VECTOR ( 31 downto 0 );
  signal axil_regs_rready  : STD_LOGIC;
  signal axil_regs_rresp   : STD_LOGIC_VECTOR ( 1 downto 0 );
  signal axil_regs_rvalid  : STD_LOGIC;
  signal axil_regs_wdata   : STD_LOGIC_VECTOR ( 31 downto 0 );
  signal axil_regs_wready  : STD_LOGIC;
  signal axil_regs_wvalid  : STD_LOGIC;
  signal rstn_slv          : STD_LOGIC_VECTOR ( 0 to 0 );

  signal disp_drv     : std_logic_vector (3 downto 0) := "1110";
  signal disp_dig     : std_logic_vector (6 downto 0);
  signal disp_data_in : std_logic_vector (3 downto 0);
  signal addr_mon     : std_logic_vector (3 downto 0);
  signal data_mon     : std_logic_vector (15 downto 0);
  signal regs_araddr  : STD_LOGIC_VECTOR ( 4 downto 0 );
  signal regs_awaddr  : STD_LOGIC_VECTOR ( 4 downto 0 );
  signal regs_rdata   : STD_LOGIC_VECTOR ( 15 downto 0 );
  signal regs_wdata   : STD_LOGIC_VECTOR ( 15 downto 0 );

begin 
  rstn <= rstn_slv(0);
  LED  <= SW;
  SSEG_AN <= disp_drv;
  SSEG_CA(6 downto 0) <= disp_dig;
  SSEG_CA(7) <= '1';   -- Digital point always off
  addr_mon   <= SW(3 downto 0);
  regs_araddr <= axil_regs_araddr(4 downto 0);
  regs_awaddr <= axil_regs_awaddr(4 downto 0);
  axil_regs_rdata(15 downto 0) <= regs_rdata;
  axil_regs_rdata(31 downto 16) <= (others => '0');
  regs_wdata <= axil_regs_wdata(15 downto 0);
  
  counter_pr: process (clk) 
  begin 
    if (rising_edge(clk)) then
      counter_reg <= counter_reg - 1;	-- decrement counter
 
      -- change active seven-segment display
      if (counter_reg = 0) then
        if (disp_drv = "0111") then
          disp_drv <= "1110";
        else
          disp_drv <= disp_drv(2 downto 0) & '1';
        end if;
      end if;
    end if;
  end process counter_pr;

  bin2_7seg_i : bin2_7seg
  port map (
    data_in  => disp_data_in,
    data_out => disp_dig
  );
  
  disp_data_in <= data_mon(3 downto 0) when disp_drv = "1110" else
                  data_mon(7 downto 4) when disp_drv = "1101" else
                  data_mon(11 downto 8) when disp_drv = "1011" else
                  data_mon(15 downto 12);
         
  bd_wrapper_i : bd_wrapper
    port map (
      axil_regs_araddr  => axil_regs_araddr      ,
      axil_regs_arprot  => open                  ,
      axil_regs_arready => axil_regs_arready     ,
      axil_regs_arvalid => axil_regs_arvalid     ,
      axil_regs_awaddr  => axil_regs_awaddr      ,
      axil_regs_awprot  => open                  ,
      axil_regs_awready => axil_regs_awready     ,
      axil_regs_awvalid => axil_regs_awvalid     ,
      axil_regs_bready  => axil_regs_bready      ,
      axil_regs_bresp   => axil_regs_bresp       ,
      axil_regs_bvalid  => axil_regs_bvalid      ,
      axil_regs_rdata   => axil_regs_rdata       ,
      axil_regs_rready  => axil_regs_rready      ,
      axil_regs_rresp   => axil_regs_rresp       ,
      axil_regs_rvalid  => axil_regs_rvalid      ,
      axil_regs_wdata   => axil_regs_wdata       ,
      axil_regs_wready  => axil_regs_wready      ,
      axil_regs_wstrb   => open                  ,
      axil_regs_wvalid  => axil_regs_wvalid      ,
      clk               => clk                   ,
      reset             => reset                 ,
      rstn              => rstn_slv              ,
      sys_clock         => sys_clock         
    );
  
  axi_regs_i : axil_regs_xil
    port map (
      s_axi_aclk        => clk                   ,
      s_axi_aresetn     => rstn                  ,
      s_axi_awaddr      => regs_awaddr           ,
      s_axi_awvalid     => axil_regs_awvalid     ,
      s_axi_awready     => axil_regs_awready     ,
      s_axi_araddr      => regs_araddr           ,
      s_axi_arvalid     => axil_regs_arvalid     ,
      s_axi_arready     => axil_regs_arready     ,
      s_axi_wdata       => regs_wdata            ,
      s_axi_wvalid      => axil_regs_wvalid      ,
      s_axi_wready      => axil_regs_wready      ,
      s_axi_bvalid      => axil_regs_bvalid      ,
      s_axi_bresp       => axil_regs_bresp       ,
      s_axi_bready      => axil_regs_bready      ,
      s_axi_rdata       => regs_rdata            ,
      s_axi_rresp       => axil_regs_rresp       ,
      s_axi_rvalid      => axil_regs_rvalid      ,
      s_axi_rready      => axil_regs_rready      ,
      addr_mon          => addr_mon              ,  
      data_mon          => data_mon

    );

end rtl;

The top implements the block design, the AXI lite registers module, and the seven segment decoder.

The main functionality of the top block as seven segment driver was explained on part 1.

The main additions from part 1 were adding the instantiation of the register blocks, and mapping the monitor addr_mon and data_mon.

TCL commands

The register values can be monitored and changed through TCL commands. To send a TCL command you must first define the command and give it a name, and then you can run it using run_hw_axi.

This is an example of a read command, to read the value of register 0:

create_hw_axi_txn read_txn0 [get_hw_axis hw_axi_1] -type read -address a0000000

And this is an example of a write command, to write a value to register 2:

create_hw_axi_txn write_txn2 [get_hw_axis hw_axi_1] -type write -address a0000004 -data 00005678 -force

To run a command, you write: run_hw_axi <cmd_name>. As an example, to run the read command defined above you issue the following TCL command:

run_hw_axi read_txn0

All the source files and a .zip file containing the complete project for Vivado 2018.2 are available on GitHub

One thought on “Magellan – a hardware monitor/debugger (II)

Leave a Reply

Your email address will not be published. Required fields are marked *