Zero simulation time

Viewed 51

I am creating some kind of RAM memory. Idea was firstly to create RAM "write" functionality, as you can see in code below. Beside RAM memory, there is RAM model driver, which was suposed to write data to RAM (just to briefly verify if write functionality works properly). RAM model driver and RAM model are connected to each other and some transaction should occur, but problem is that simulation is completed within zero simulation seconds. Anybody has idea what could be a problem?

@gear
def ram_model(write_addr: Uint,
              write_data: Queue['dtype'],*,
              ram_mem = None,
              dtype = b'dtype',
              mem_granularity_in_bytes = 1) -> (Queue['dtype']):

    if(ram_mem is None and type(ram_mem) is not dict):
        ram_mem = {}

    ram_write_op(write_addr = write_addr,
                 write_data = write_data,
                 ram_memory = ram_mem)

@gear
async def ram_write_op(write_addr: Uint,
                       write_data: Queue,*,
                       ram_memory = None,
                       mem_granularity_in_bytes = 1):

    if(ram_memory is None and type(ram_mem) is not dict):
        SystemError("Ram memory is %s but it should be dictionary",(type(ram_memory)))

    byte_t = Array[Uint[8], mem_granularity_in_bytes]

    async with write_addr as addr:
        async for data, _ in write_data:
            for b in code(data, byte_t):
                ram_memory[addr] = b
                addr += 1

@gear
async def ram_model_drv(*,addr_bus_width = b'asize',
                        data_type = b'dtype') -> (Uint[8], Queue['data_type']):

    num_of_w_comnds = 15
    matrix = np.random.randint(10, size = (num_of_w_comnds, 10))
    for command_id in range(num_of_w_comnds):
        for i in range(matrix[command_id].size):
            yield (command_id, (matrix[command_id][i], i == matrix[command_id].size))


stimul = ram_model_drv(addr_bus_width = 8, data_type = Fixp[8,8])
out = ram_model(stimul[0], stimul[1])

sim()

Here is the output message:

python ram_model.py
-                      [INFO]: Running sim with seed: 3934280405122873233  
0                      [INFO]: -------------- Simulation start --------------  
0                      [INFO]: ----------- Simulation done ---------------  
0                      [INFO]: Elapsed: 0.00  
1 Answers

Yeah, this one is a bit convoluted. Gist of the issue is that in the ram_model_drv module you are synchronously outputting data on both of its output interfaces with they yield statement. For PyGears this means that you would like data on both of these interfaces acknowledged before continuing. The ram_write_op module is connected to both of these interfaces via write_addr and write_data agruments. Inside that module, you acknowledge data from write_addr interface only after you've read multiple data from write_data interface, hence there's a deadlock and PyGears simulator detects that no further simulation steps are possible and exits at the end of the step 0.

There are also two additional issues in the driver:

  1. It will never generate an eot for the output data Queue. Instead eot should be generated when i == matrix[command_id].size - 1.
  2. The async modules are run in an endless loop by PyGears, so your ram_model_drv will generate the data endlessly unless you explicitly generate a GearDone exception.

OK, back to the main issue. There are several possibilities to circumvent it:

  1. Use decoupling

    For this to work, you first need to split data output in two yield statements, one for the write_addr and the other for the write_data, since your ram_write_op will use only one address per several write data.

    @gear
    async def ram_model_drv(*, addr_bus_width, data_type) -> (Uint[8], Queue['data_type']):
    
        num_of_w_comnds = 15
        matrix = np.random.randint(10, size=(num_of_w_comnds, 10))
        for command_id in range(num_of_w_comnds):
            yield (command_id, None)
            for i in range(matrix[command_id].size):
                yield (None, (matrix[command_id][i], i == matrix[command_id].size - 1))
    
        raise GearDone
    

    You can use either dreg or decouple modules to temporarily store output data from ram_model_drv before they are consumed by ram_write_op.

    out = ram_model(stimul[0] | decouple, stimul[1] | decouple)

  2. Split driver into two modules, one driving each of the two interfaces

  3. Use low level synchronization API for interfaces

    Underneath the yield mechanism, there is a lower level API for communicating via PyGears interfaces. Handles to output interfaces can be obtained via module().dout field. Data can be sent via interface without waiting for it to be acknowledged using put_nb() method. Later, in order to wait for the aknowledgment, ready() method can be awaited. Finally, put() method combines the two in one call, so it will both send the data and wait for the acknowledgement.

    @gear
    async def ram_model_drv(*,
                            addr_bus_width=b'asize',
                            data_type=b'dtype') -> (Uint[8], Queue['data_type']):
    
        addr, data = module().dout
    
        num_of_w_comnds = 15
        matrix = np.random.randint(10, size=(num_of_w_comnds, 10))
        for command_id in range(num_of_w_comnds):
            addr.put_nb(command_id)
    
            for i in range(matrix[command_id].size):
                await data.put((matrix[command_id][i], i == matrix[command_id].size - 1))
    
            await addr.ready()
    
        raise GearDone
    
Related