Package: axilent
axilentaxilent provides python tools for describing a sequence of Axi4Lite command
in python.A python test can be written describing the sequence of commands to be sent
and the expected responses.  This test can then be run against both a
simulation of the design and against the implemented design running on an
FPGA.axilent is structured so that tests can be run both against both interactive
simulations and simulations where inputs cannot depend on outputs such as
file-based testbenchs.HandlersCurrently there ishandlerfor working with file-based testbenchs
generated byslvcodecand
for simulations on FPGAs run over JTAG usingpyvivado.
In the likely event you are using neither of these methods, then you
will have to write you own handler. The handler has a single required
method.handler.send(command)Sends a command to the FPGA, or adds the command to the list of commands that
will be run in the simulation.The handler also processes the responses from the simulation or FPGA and updates thecommand.futurevalues appropriately.  It calls thecommand.process_responsesmethod
to help process the responses.CommandsACommandobject represents a simple axi4lite read or write, or a more complex
aggregrate consisting of many read and write commands.command.get_axi_commands()Returns a list ofAxiCommandobjects.  EachAxiCommandobject is a simple
axi4lite read or write command to a single address or a range of addresses.command.process_responses(read_responses, write_responses, resolve_future)Processes two lists ofAxiResponseobjects, one is a list of responses to
write requests and one is a list of responses to read requests.  The two lists
must begin with the responses to this command, and these entries are removed
from the lists.
An (exception, result) tuple is returned by the function.  Ifresolve_futureis True then the commands future is resolved with the result and exception.As an example consider an AxiAdder module, which uses an Axi4Lite interface.  We can
write to registers A and B, and when we read from register C the returned result is
the sum of the contents in regsiters A and B.  We can create aCommandobject to
represent using this hardware to add two numbers.classAddNumbersCommand(comms.CombinedCommand):'''
    A command that writes to the intA and intB registers
    and then reads from the intC register.
    The effect is the add the two inputs.
    '''def__init__(self,a,b,addresses):write_a_command=comms.SetUnsignedCommand(address=addresses['intA'],value=a,description='Setting A in AddNumbers',)write_b_command=comms.SetUnsignedCommand(address=addresses['intB'],value=b,description='Setting B in AddNumbers',)read_c_command=comms.GetUnsignedCommand(address=addresses['intC'],description='Getting C from AddNumbers',)commands=(write_a_command,write_b_command,read_c_command)super().__init__(description='Add 2 numbers with AddNumber',commands=commands)defprocess_responses(self,read_responses,write_responses,resolve_future=True):'''
        Return the third response (from the final read command)
        Don't return any errors.
        '''e,result=super().process_responses(read_responses,write_responses,resolve_future=False)intc=result[2]ifresolve_future:self.resolve_future(e,intc)returne,intcCommTypically command objects are hidden from the testing interface by wrapping them
with a Comm object.  Methods on this comm object createCommandobjects, send
them to a handler, and return the command futures.classAxiAdderComm:'''
    Class to communicate with the AxiAdder module.
    '''INTA_ADDRESS=0INTB_ADDRESS=1INTC_ADDRESS=2def__init__(self,address_offset,handler):'''
        `address_offset` is any addition that is made to the address that is
        consumed during routing.
        `handler` is the object responsible for dispatching the commands.
        '''self.handler=handlerself.address_offset=address_offsetself.addresses={'intA':address_offset+self.INTA_ADDRESS,'intB':address_offset+self.INTB_ADDRESS,'intC':address_offset+self.INTC_ADDRESS,}defadd_numbers(self,a,b):'''
        A complex complex command that write to two registers and
        then reads from another.
        Sets 'a' and 'b' then reads 'c' (should be a+b)
        '''command=AddNumbersCommand(a,b,self.addresses)self.handler.send(command)returncommand.futureTestsA possible way to write a test to is define a class with apreparemethod that
defines the requests to send to the module, and acheckmethod that analyzes
the responses.Thepreparemethod uses ahandlerto generate the requests and creates a
number of futures to hold the results of processing the responses.The responses are then processed by a handler-dependent method and then thecheckmethod can be run to check the contents of the resolved futures.classAxiAdderTest(object):def__init__(self):self.expected_intcs=[]self.intc_futures=[]defprepare(self,handler):'''
        Sends a number of 'add_numbers' commands.
        '''comm=AxiAdderComm(address_offset=0,handler=handler)n_data=20max_int=pow(2,16)-1logger.debug('preparing data')foriinrange(n_data):inta=random.randint(0,max_int)intb=random.randint(0,max_int)self.expected_intcs.append(inta+intb)future=comm.add_numbers(inta,intb)self.intc_futures.append(future)# Flush the communication for simulations.# Ignored in FPGA.handler.send(comms.FakeWaitCommand(clock_cycles=10))defcheck(self):'''
        Check that the output of the commands matches the expected values.
        '''output_intcs=[f.result()forfinself.intc_futures]assertoutput_intcs==self.expected_intcsprint('Success!!!!!!!!!!!!!!!')Repeatability of SimulationsAlthough the simulations are repeatable the FPGA-based tests are currently not
repeatable because of the changing number of clock-cycles between when requests
are received.
I would like to fix this by allowing the ability of specify on which clock
cycle at AXI request should be sent (they would be gathered in a delayed in a buffer
on the FPGA until the correct clock cycle).
TODO: Add delaying of requests to allow repeatability.