cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Highlighted
Anonymous
Not applicable
1,085 Views

Multidriven AXI-Lite registers in packaged IP-Core

Hello,

I am working on a task that involves packaging  custopm IP and connecting it to MicroBlaze. For this purpose I try to use the AXI-Lite Slave with 5 registers: 1 for writing (slv_reg0) and the the rest for reading.

I watched and followed the Qucktake video: www.xilinx.com%2Fvideo%2Fhardware%2Fcreating-an-axi-peripheral-in-vivado.html&usg=AOvVaw0TTyGOwu_wsbS00VheX6j2

I added a few ports and instantiated my module:

 

entity lite_keyboard_timer_v1_0_S00_AXI is
	generic (
		-- Users to add parameters here

		-- User parameters ends
		-- Do not modify the parameters beyond this line

		-- Width of S_AXI data bus
		C_S_AXI_DATA_WIDTH	: integer	:= 32;
		-- Width of S_AXI address bus
		C_S_AXI_ADDR_WIDTH	: integer	:= 5
	);
	port (
		-- Users to add ports here
SCLK : in STD_LOGIC;
                SDIN : in STD_LOGIC;
                Reset_driver:in std_logic;
                error_out:out std_logic;
		-- User ports ends
		-- Do not modify the ports beyond this line

		-- Global Clock Signal
		S_AXI_ACLK	: in std_logic;
		-- Global Reset Signal. This Signal is Active LOW
		S_AXI_ARESETN	: in std_logic;
		-- Write address (issued by master, acceped by Slave)
		S_AXI_AWADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		-- Write channel Protection type. This signal indicates the
    		-- privilege and security level of the transaction, and whether
    		-- the transaction is a data access or an instruction access.
		S_AXI_AWPROT	: in std_logic_vector(2 downto 0);
		-- Write address valid. This signal indicates that the master signaling
    		-- valid write address and control information.
		S_AXI_AWVALID	: in std_logic;
		-- Write address ready. This signal indicates that the slave is ready
    		-- to accept an address and associated control signals.
		S_AXI_AWREADY	: out std_logic;
		-- Write data (issued by master, acceped by Slave) 
		S_AXI_WDATA	: in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		-- Write strobes. This signal indicates which byte lanes hold
    		-- valid data. There is one write strobe bit for each eight
    		-- bits of the write data bus.    
		S_AXI_WSTRB	: in std_logic_vector((C_S_AXI_DATA_WIDTH/8)-1 downto 0);
		-- Write valid. This signal indicates that valid write
    		-- data and strobes are available.
		S_AXI_WVALID	: in std_logic;
		-- Write ready. This signal indicates that the slave
    		-- can accept the write data.
		S_AXI_WREADY	: out std_logic;
		-- Write response. This signal indicates the status
    		-- of the write transaction.
		S_AXI_BRESP	: out std_logic_vector(1 downto 0);
		-- Write response valid. This signal indicates that the channel
    		-- is signaling a valid write response.
		S_AXI_BVALID	: out std_logic;
		-- Response ready. This signal indicates that the master
    		-- can accept a write response.
		S_AXI_BREADY	: in std_logic;
		-- Read address (issued by master, acceped by Slave)
		S_AXI_ARADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		-- Protection type. This signal indicates the privilege
    		-- and security level of the transaction, and whether the
    		-- transaction is a data access or an instruction access.
		S_AXI_ARPROT	: in std_logic_vector(2 downto 0);
		-- Read address valid. This signal indicates that the channel
    		-- is signaling valid read address and control information.
		S_AXI_ARVALID	: in std_logic;
		-- Read address ready. This signal indicates that the slave is
    		-- ready to accept an address and associated control signals.
		S_AXI_ARREADY	: out std_logic;
		-- Read data (issued by slave)
		S_AXI_RDATA	: out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		-- Read response. This signal indicates the status of the
    		-- read transfer.
		S_AXI_RRESP	: out std_logic_vector(1 downto 0);
		-- Read valid. This signal indicates that the channel is
    		-- signaling the required read data.
		S_AXI_RVALID	: out std_logic;
		-- Read ready. This signal indicates that the master can
    		-- accept the read data and response information.
		S_AXI_RREADY	: in std_logic
	);
end lite_keyboard_timer_v1_0_S00_AXI;
component keyboard_timer is
        generic(
            --mem_size: natural:=300;
            clk_div: natural:=100_000
        );
        Port ( clk : in STD_LOGIC;
            SCLK : in STD_LOGIC;
            SDIN : in STD_LOGIC;
            Reset : in STD_LOGIC;
            Reset_driver:in std_logic;
            requested_addr:in std_logic_vector(8 downto 0);
            --requested_addr:in natural range 0 to mem_size;
            char_out: out std_logic_vector(7 downto 0);
            time_pressed: out std_logic_vector(31 downto 0);
            time_until_next: out std_logic_vector(31 downto 0);
            valid_address: out unsigned(8 downto 0);
            --button_out: out BUTTON_DATA;
            error_out:out std_logic
        );
end component;

 

 

Just like in video:

 

 -- Add user logic here
my_kt:keyboard_timer
port map
(
clk=>S_AXI_ACLK,
SCLK=>SCLK,
SDIN=>SDIN,
Reset=>reset_timer,
Reset_driver=>Reset_driver,
requested_addr=>slv_reg0(8 downto 0),
char_out=>slv_reg1(7 downto 0),
time_pressed=>slv_reg2,
time_until_next=>slv_reg3,
std_logic_vector(valid_address)=>slv_reg4(8 downto 0),
error_out=>error_out
);
reset_timer<= not S_AXI_ARESETN;
-- User logic ends

 

 

How after trying to implement the block design with the IP Core, I got errors that registers slv_reg1 - slv_reg4 are multidriven.

That is because in additon to the port map, the registers a driven in this process:

-- Implement memory mapped register select and write logic generation
	-- The write data is accepted and written to memory mapped registers when
	-- axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
	-- select byte enables of slave registers while writing.
	-- These registers are cleared when reset (active low) is applied.
	-- Slave register write enable is asserted when valid address and data are available
	-- and the slave is ready to accept the write address and write data.
	slv_reg_wren <= axi_wready and S_AXI_WVALID and axi_awready and S_AXI_AWVALID ;

	process (S_AXI_ACLK)
	variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0); 
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      slv_reg0 <= (others => '0');
	      slv_reg1 <= (others => '0');
	      slv_reg2 <= (others => '0');
	      slv_reg3 <= (others => '0');
	      slv_reg4 <= (others => '0');
	    else
	      loc_addr := axi_awaddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
	      if (slv_reg_wren = '1') then
	        case loc_addr is
	          when b"000" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 0
	                slv_reg0(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"001" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 1
	                slv_reg1(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"010" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 2
	                slv_reg2(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"011" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 3
	                slv_reg3(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"100" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 4
	                slv_reg4(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when others =>
	            slv_reg0 <= slv_reg0;
	            slv_reg1 <= slv_reg1;
	            slv_reg2 <= slv_reg2;
	            slv_reg3 <= slv_reg3;
	            slv_reg4 <= slv_reg4;
	        end case;
	      end if;
	    end if;
	  end if;                   
	end process; 

Apparently, the presenter in the video made quiet adjustments to the process, which he failed to mentioned (otherwise he should've got the same problem with the multidriven net)

 

So how the process should be changed to resolve the issue?

0 Kudos
10 Replies
Highlighted
Anonymous
Not applicable
1,032 Views

Is the question unclear? Then please ask for clarification.

0 Kudos
Highlighted
Observer
Observer
480 Views
Registered: ‎07-10-2019

Hello!

I have currently the same problem as you had, did you finally solve the issue? Could you post the solution please?

 

Best Regards,

0 Kudos
Highlighted
Visitor
Visitor
345 Views
Registered: ‎03-28-2020

Hi

same problem here. I also have issues with multi driven nets using the AXI-Lite IP template in VIVADO 2020.1. 

I am quite sure the issue is on my end. Just I don't find any example that teaches me on, how to write back the results from my own modules to the AXI-Lite registers.

 

I understand the warning/error in that way: You are not allowed to write to the slv_reg from user code, while the axi-interface is writing data to it. So I need to make my part of the code aware when a W/R-opperation to that slv_reg is happening. Just during cycles the slv_reg is untouched I am allowed to update it from my code

 

Any example on that available in the documentation?

0 Kudos
Highlighted
Adventurer
Adventurer
323 Views
Registered: ‎04-04-2018

The issue you are seeing is due to slv_reg0-slv_reg3 being axi write/read registers in the default design. It looks slv_reg4 was added as a write/read register as well. Registers are read back in this process:

process (slv_reg0, slv_reg1, slv_reg2, slv_reg3, axi_araddr, S_AXI_ARESETN, slv_reg_rden)
variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0);
begin
-- Address decoding for reading registers
loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
case loc_addr is
when b"00" =>
reg_data_out <= slv_reg0;
when b"01" =>
reg_data_out <= slv_reg1;
when b"10" =>
reg_data_out <= slv_reg2;
when b"11" =>
reg_data_out <= slv_reg3;
when others =>
reg_data_out <= (others => '0');
end case;
end process;

You can use slv_reg0 as you are, but you need to create 4 additional readback registers(slv_reg4-slv_reg7) and use those to read the outputs of your module.

 

 

Steve Markgraf - Distinguished FPGA Design & Support Engineer E5-E
www.designlinxhs.com
Tags (2)
0 Kudos
Highlighted
Scholar
Scholar
311 Views
Registered: ‎05-21-2015

@TobiasWeber1,

Do beware that both Vivado generated IP AXI-slave (AXI-lite and AXI) cores are broken and known for causing designs to hang under certain circumstances.  They've been broken since at least 2016.3, and remain broken as of 2020.1.  (Ref AXI-Lite slave, AXI slave)

The AXI stream master is similarly broken.

Dan

0 Kudos
Highlighted
Visitor
Visitor
245 Views
Registered: ‎03-28-2020

Hi,

please find attached a solution that is working for me. Basically I am routing the slv_regs directly to output ports, which I connect to my own module, which is instantiated on the same level as the instance of the AXI-Lite interface. With that I am following the example given here: https://www.beyond-circuits.com/wordpress/tutorial/tutorial13/ 

What is strange about the Xilinx example (But I am a newbie regarding verilog). Usually all assignments to an register/variable should happen with in one always block (fulfilled). But also all conditions need to covered with this always block (not fulfilled). Do me these rules actually result in. You need to interleave your custom logic with the AXI-Interface and you need to extend the AXI-Interface to cover more states. As this looks impractical to me to mix own and external code, I opted for the output - input loop. 

But there is still an issue. The peripheral works as intended, but one of the registers (reg_slv0 / ctr1) is not reading back stable. The behavior of the system is as expected, so the write operation to reg_slv0 / ctr1 has the desired effect. But it sometime takes a while (immediately, short, long or for ever) to reflect that changes on a read-back from that register (slv_reg0_back / ctr1_back). All other registers work well.  

Dan I trust you that the Xilinx example on the AXI-Lite is not standard compliant. However, performing back-to-back register reads for hours, using just one bus-master (microblaze, running with the same frequency as my peripheral) was stable, regarding the bus transfers. No hang-up, 

But still it isn't perfect, as I have an issue with read-back of this one register.

 

Please see the C-code for communicating with the IP.

int main()
{
    int success;
    success = platform_init();
    if(success != XST_SUCCESS) {
    	xil_printf("Error on initializing the platform");
    }

    print("Setup Ethernet Interface ");
    eth_init();
    IMUs_init();


    //Configure TIM1 & TIM2 (set prescaler and reset) base clock is 100Mhz 
    Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_PSC1_OFFSET, 100);    //1us
    Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_PSC2_OFFSET, 100000); //1ms
    Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET, 1);
    Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET, 1);

    initFrame();
    cycleStatus = READY;
    while(1) {
    	cycleStart();
    	gpioInputs();

    	//Start || Pause TIM1
    	if( GPIO_IS_ON_ANY(gpio0_inp, (SW3) ) ) {
    		Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET, 2);
    	} else {
    		Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET, 0);
    	}
    	
    	//Start || Pause TIM2
    	if( GPIO_IS_ON_ANY(gpio0_inp, (SW2) ) ) {
			Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET, 2);
		} else {
			Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET, 0);
		}
    	
    	//Start || Pause TIM1
    	if( GPIO_IS_ON_ANY(gpio0_inp, (BTN3) ) ) {
    		uint32_t ctr;
    		ctr = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET);
    		Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET, (ctr|1u) );
    		ctr = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET);
    		Xil_Out32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET, (ctr|1u));
    	}

    	//My stuff
    	if(cycleStatus == RUN_AND_SENT) {
			IMUs_read(frame.imuValues);
			UDP_send((uint8_t*)&frame, sizeof(frame));

    	}

    	//print timer to console
    	if( GPIO_IS_ON_ANY(gpio0_inp, (SW1) ) ) {
    		uint32_t t1, t2, c1, c2;
    		t1 = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CNT1_OFFSET);
    		t2 = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CNT2_OFFSET);
    		c1 = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR1_OFFSET); //Instable read of this value!!!
    		c2 = Xil_In32(PLATFORM_CLK_BASEADDR + PLATFORM_CLK_CTR2_OFFSET);


    		xil_printf( "%u ", getSystemTime_ms() ); //old timer using FIT + AXI-Timer combinations (lot of overhead)
    		xil_printf( "%u:%u ", t1,c1 );			 //TIM1 counter and control register
    		xil_printf( "%u:%u\n\r", t2,c2 );		 //TIM2 counter and control register
    	}
    	eth_runThread();
    	gpioOutputs();
    	cycleEnd();
    }

    platform_cleanup();
    return 0;
}
0 Kudos
Highlighted
Scholar
Scholar
229 Views
Registered: ‎05-21-2015

@TobiasWeber1,

Well, okay, let's take a look at that example you posted.  Specifically, let's look at how it handles the AXI protocol, and whether or not it follows protocol.

The answer is, it doesn't.

read-bug-annotated.png

But sure, I get it, you tested it against back-to-back reads for hours and didn't find any faults in it.  That's part of the reason why this bug has lived for so many years without getting fixed.  As far as I can tell, the problem is hidden by the default bus interconnect which prevents slaves from experiencing any backpressure.  The problem then is, what happens when you "upgrade" your interconnect so that it becomes a true crossbar?  Your design will then fail and you will blame the crossbar implementation for that failure.

However, if you dig a bit deeper, you'll find users have been complaining about their designs hanging for years--based upon what appears to be either this bug or a correspondingly similar one in Xilinx's AXI (not lite) slave core.

You should also know--I didn't test your core against "back to back register reads for hours".  I ran a formal verification test on your core that took 2 seconds to return the trace above.  The tools are free and open.  The AXI-lite property set I used is also posted on Github--it's a lot easier than hours of simulation that won't find a bug.

As for the performance, yeah, I get it.  Your point is fair and valid.  Not everyone needs 100% throughput, so why pay for it?  If it meets spec, it's good enough.  Well, let's take a look at your design anyway to see what sort of performance you are getting--just to do due diligence in documenting this core.

Write performance:

write-cover-annotated.png

Looks like you are getting one write through every third clock cycle.

How about read performance?

read-cover-annotated.png

Looks like one read gets through every other clock cycle.

That kind of performance will work for many applications.  It's nothing to write home about, but it will easily get you through many types of tasks.

At the same time, you might find this logic not only gets better performance, but it's also bus compliant and (in the comparable non-skidbuffer case) uses fewer LUTs.

Your call.  Personally, if it were my core, I'd at least fix the protocol bug lest I get stuck in FPGA Hell later on when I'm not expecting it.  Consider the case of the software programmer who changed his MicroBlaze software only to cause the design to fail later on--this bug will show up when you are least expecting it.

Dan

 

Tags (1)
Highlighted
Visitor
Visitor
129 Views
Registered: ‎03-28-2020

Hi Dan,

thanks a lot for your explanation and the backgrounds. I am going to have a look into your implementations for my next IP-core, which should be an SPI-3 to AXI core.

 

Best regards,

Tobias

0 Kudos
Highlighted
Visitor
Visitor
99 Views
Registered: ‎03-28-2020

Okay I guess it's now biting me. 

https://forums.xilinx.com/t5/Processor-System-Design-and-AXI/MicroBlaze-Exception-00100-Data-bus-error-exception-debug/m-p/1151304/highlight/true#M54414 

 

Just using Xilinx provided IP on a SPI communication, but a slightly optimized driver to fill the FIFOs faster is causing a bus exception..... The comment left by the developer of the XILINX SPI driver indicate that they knew their implementation is horribly slow, as they check FIFO-TX full after each write. In my case I calculate the chunk size before and after. So I have many more successive writes in a "burst" (I know AXI-Lite is not supporting burst. So I am handing in one byte after the other) 

0 Kudos
Highlighted
Scholar
Scholar
87 Views
Registered: ‎05-21-2015

@TobiasWeber1,

Judging from your link, it looks like the problem is that the core is designed to return a bus error that you aren't expecting.  This is valid according to the bus protocol, and fairly easily debugged.

The part that's more challenging is handling the *custom* IP demo RTL, which is broken, and which will hang the bus.  That will leave you not knowing what part of your design had the bug.  At least in this case, with the AXI Quad IP core, it's easily chased down to an attempt to drive the IP faster than it's able to go.

Dan

0 Kudos