5. Hardware Interfaces

5.1. Hardware Interfaces

5.1.1. SLAVE_CONTROLLER_INTERFACE

The SLAVE_CONTROLLER module facilitates communication between the host CPU and multiple engine cores through 6 registers. These registers are used to send commands to the engine cores, with a specific format for handling operations and context data.

The lgoic signals and registers are influenced by the following generics:

Name

Type

Default

Description

AXI_DATA_WIDTH

integer

32

Specifies the width of the data bus, which is the size of each register.

NUMBER_OF_CORES

integer

1

Defines the number of engine cores connected to the SLAVE_CONTROLLER.

The following ports allow communication with the engine cores:

Signal Name

Direction

Width

Description

head_reg_2_out

out

AXI_DATA_WIDTH

Register used for sending operation codes (op-codes).

payload_3_out

out

AXI_DATA_WIDTH

Register used for context-specific information.

payload_4_out

out

AXI_DATA_WIDTH

Register used for context-specific information.

payload_5_out

out

AXI_DATA_WIDTH

Register used for context-specific information.

payload_6_out

out

AXI_DATA_WIDTH

Register used for context-specific information.

payload_7_out

out

AXI_DATA_WIDTH

Register used for context-specific information.

Signals for communication status:

Signal Name

Direction

Width

Description

c_new_command

out

NUMBER_OF_CORES

Indicates a new command for the cores.

c_command_complete

in

NUMBER_OF_CORES

Signals when a core has completed executing a command.

The SLAVE_CONTROLLER targets more than only one engine core. The number is determined by the generic NUMBER_OF_CORES. Each bit of c_new_command and c_command_complete corresponds to one engine core.

5.1.2. MASTER_CONTROLLER_INTEFACE

The MASTER_CONTROLLER module interfaces between multiple engine cores and external DDR memory, handling simultaneous memory transaction requests (read and write) from different cores. It prioritizes requests based on two main rules:

  1. Core priority: The core with the lowest index has the highest priority.

  2. Request priority: Write requests are prioritized over read requests within a core.

The module has three configurable parameters to adjust for various system requirements:

Name

Type

Default

Description

AXI_DATA_WIDTH

integer

32

Specifies the width of the data bus between the cores and the memory.

AXI_ADDR_WIDTH

integer

32

Defines the address width of the memory being accessed.

NUMBER_OF_CORES

integer

1

Determines the number of cores that will interface with the memory controller.

The following ports connect to the engine cores, enabling read and write transactions:

Signal Name

Direction

Width

Description

c_rd_req

in

NUMBER_OF_CORES

Read request signals from each core.

c_wr_req

in

NUMBER_OF_CORES

Write request signals from each core.

raddr

in

NUMBER_OF_CORES * AXI_ADDR_WIDTH

Read addresses from each core.

waddr

in

NUMBER_OF_CORES * AXI_ADDR_WIDTH

Write addresses from each core.

out_data

out

AXI_DATA_WIDTH

Data output from the core to the memory.

in_data

in

NUMBER_OF_CORES * AXI_DATA_WIDTH

Data input from the memory to each core.

c_wxfer_length

in

NUMBER_OF_CORES * 12

Transfer byte lengths for write operations for each core.

c_rxfer_length

in

NUMBER_OF_CORES * 12

Transfer byte lengths for read operations for each core.

c_next_wdata

out

NUMBER_OF_CORES

Indicates when a core should provide the next word for a write operation.

c_next_rdata

out

NUMBER_OF_CORES

Indicates when memory should provide the next word for a read operation.

c_done

out

1

Indicates completion of the current transaction.

Every core takes a specific portion of the signals. Each engine core connected to a port with the standard logic vector ..

  • (NUMBER_OF_CORES - 1 downto 0) takes 1 bit.

  • (NUMBER_OF_CORES * AXI_ADDR_WIDTH - 1 downto 0) takes AXI_ADDR_WIDTH bits.

  • (NUMBER_OF_CORES * AXI_DATA_WIDTH - 1 downto 0) takes AXI_DATA_WIDTH bits.

The transaction has essentially 3 states

  • IDLE: The MASTER_CONTROLLER waits for a read or write request

  • START_TXN: The MASTER_CONTROLLER prioritizes the engine core and write over read requests

  • TXN: The MASTER_CONTROLLER performs the transaction

For the engine core the following aspects need to be taken care of

Sart a write request: Set the following singals until the write request has started

  • set c_wr_req to ‘1’ to indicate a write request

  • set c_wxfer_length which is the number of bytes to be send to the memory. It ranges from 4 * 2^3 to 4 * 2^15, where 4 is the 4 bytes of a 32 bit word.

  • set waddr which is the write target address of the external memory

  • set in_data which is the first data word to send to external memory

Write transaction: wait until the core asks for new data when c_next_wdata is ‘1’. This means the MASTER_CONTROLLER has accepted the request and starts the transaction

  • c_wxfer_length is irrelevant

  • waddr is irrelevant

  • c_wr_req can be set to ‘0’ to not start a new request after the transaction has finished

  • c_next_wdata on high indicates whenever in the next clock cycle a new word is written. Make sure to provide the next data word at in_data

  • c_done on high indicates that a write or read transaction has been completed. Since c_next_wdata was triggered before you know its a write transaction.

Sart a read request: Set the following singals until the read request has started

  • set c_rd_req to ‘1’ to indicate a read request

  • set c_rxfer_length which is the number of bytes to be send to the memory. It ranges from 4 * 2^3 to 4* 2^15, where 4 is the 4 bytes of a 32 bit word.

  • set waddr which is the read target address of the external memory

Read transaction: wait until the core asks for new data when c_next_wdata is ‘1’. This means the MASTER_CONTROLLER has accepted the request and starts the transaction

  • c_wxfer_length is irrelevant

  • waddr is irrelevant

  • c_wr_req can be set to ‘0’ to not start a new request after the transaction has finished

  • c_next_wdata on high indicates whenever in the next clock cycle a new word is written. Make sure to provide the next data word at in_data

  • c_done on high indicates that a write or read transaction has been completed. Since c_next_wdata was triggered before you know its a write transaction.

A diagramm demonstrating a write request

A diagramm demonstrating a read request

5.2. Software Interfaces

5.2.1. SLAVE_CONTROLLER_INTERFACE

Send a command to an engine core:

The following code examples how the SLAVE_CONTROLLER receives a new command. Besides the command send logic, there are 6 more register that provide command context. For example context look into the AXI_TEST_ENGINE_CORE_INTERFACE.

Here the following two signals matter:

  • control_command_reg_0_in (reg_data[0]) : A write only register Each bit belongs to one engine core. Be aware that there are often more bits provided than engine cores are connected. When the bit targeting the engine core is set to ‘1’ the corresponding engine core receives a new command. Make sure to set the bit afterwards to 0. A new command can only be send if the bit has been 0 before.

  • control_status_1_out (reg_data[1]): A read only register Each bit belongs to one engine core. Be aware that there are often more bits provided than engine cores are connected. The SLAVE_CONTROLLER sets a bit to ‘1’ after a command has been send to the corresponding engine core. It indicates that the engine core is busy and wont process new requests

In vhdl:

-- <<Here is place to set the other register for command context>>
-- Send a command to the first engine core with the index 0
control_command_reg_0_in(0) <= '1';
-- Wait for a clock cycle using the THANNA_TEST_UTILS package
run_cycle(clk);
-- Set the send command bit for the engine core 0 back to 0
control_command_reg_0_in(0) <= '0';
-- Wait until the status register at index 0 is '0' which indicates that
-- the engine core has completed the command and is not busy anymore
while control_status_1_out(0) = '0' loop run_cycle(clk); end loop;

In C:

// <<Here is place to set the other register for command context>>
// Send a command to the first engine core with the index 0
reg_data[0] = 1;
// Set the send command bit for the engine core 0 back to 0
reg_data[0] = 0;
// Wait until the status register at index 0 is '0' which indicates that
// the engine core has completed the command and is not busy anymore
printf("WAIT: \n\r");
while ((reg_data[1] & 1) != 1)
{   // Looping = waiting
    for(size_t i = 0; i < 1000000; i++);
}
printf("Read complete");

5.2.2. AXI_TEST_ENGINE_CORE_INTERFACE

The following examples show how to provide the command context for the AXI_TEST engine core. To send the command append the send command logic from the SLAVE_CONTROLLER_INTERFACE.

The following AXI_DATA_WIDTH generic defines the size of the register, which depend on the devices data bus. In the case of the Zybo Z-720 it is 32 Bit. In the case of AXI_TEST the register are used as follows:

Register

Write/Read

Width

Description

head_reg_2_out (reg[2])

write

AXI_DATA_WIDTH

Register used for sending operation codes (op-codes).

payload_3_out (reg[3])

write

AXI_DATA_WIDTH

Register containing the write or read address for a memory transaction.

payload_5_out (reg[5])

write

AXI_DATA_WIDTH

Start index of the burst register in the AXI_TEST module. Only relevant for memory transactions.

payload_6_out (reg[6])

write

AXI_DATA_WIDTH

Number of burst bytes transferred in a memory transaction. It can range from 4 * 2^2 to 4 * 2^15.

Read command:

In vhdl:

head_reg_2_in <= std_logic_vector(to_unsigned(2, AXI_DATA_WIDTH)); --  Op code for the read command
payload_3_in  <= (others => '0'); -- Address initialized with a pointer pointing to free space in the DDR
payload_5_in  <= (others => '0'); -- Start index of the burst register in the AXI_TEST module
payload_6_in  <= std_logic_vector(to_unsigned(4 * 8, AXI_DATA_WIDTH)); -- Number of burst bytes. It can range from 4 * 2^2 to 4 * 2^15

In C:

reg_data[2] = 2;                  // Op code for the read command
reg_data[3] = (uint32_t)ddr_data; // Address initialized with a pointer pointing to free space in the DDR
reg_data[5] = 0;                  // Start index of the burst register in the AXI_TEST module
reg_data[6] = 8;                  // Number of burst bytes. It can range from 4 * 2^2 to 4 * 2^15

Write command:

In vhdl:

head_reg_2_in <= std_logic_vector(to_unsigned(1, AXI_DATA_WIDTH)); -- Op code for the write command
payload_3_in  <= (others => '0'); -- Address initialized with a pointer pointing to free space in the DDR
payload_5_in  <= (others => '0'); -- Start index of the burst register in the AXI_TEST module
payload_6_in  <= std_logic_vector(to_unsigned(4 * 8, AXI_DATA_WIDTH)); -- Number of burst bytes. It can range from 4 * 2^2 to 4 * 2^15.

In C:

reg_data[2] = 1;                  // Op code for the write command
reg_data[3] = (uint32_t)ddr_data; // Address initialized with a pointer pointing to free space in the DDR
reg_data[5] = 0;                  // Start index of the burst register in the AXI_TEST module
reg_data[6] = 8;                  // Number of burst bytes. It can range from 4 * 2^2 to 4 * 2^15.

Increment command:

In vhdl:

head_reg_2_in <= std_logic_vector(to_unsigned(16, AXI_DATA_WIDTH)); -- Op code for the increment command

In C:

reg_data[2] = 16;                 // Op code for the increment command