diff --git a/TARGET=sim b/TARGET=sim new file mode 100644 index 000000000..3e6eaef1f --- /dev/null +++ b/TARGET=sim @@ -0,0 +1 @@ +$You are executing from: /home/fabian/Desktop/Bachelor_Project/x-heep diff --git a/core-v-mini-mcu.core b/core-v-mini-mcu.core index aef1f20e2..676b4ac48 100644 --- a/core-v-mini-mcu.core +++ b/core-v-mini-mcu.core @@ -36,6 +36,7 @@ filesets: - x-heep:ip:fast_intr_ctrl - x-heep:ip:obi_fifo - x-heep:ip:pdm2pcm + - x-heep:ip:obi_spi_slave files: - hw/core-v-mini-mcu/core_v_mini_mcu.sv - hw/core-v-mini-mcu/cpu_subsystem.sv @@ -94,6 +95,7 @@ filesets: - hw/system/x_heep_system.vlt - hw/simulation/simulation.vlt - hw/ip/i2s/i2s.vlt + - hw/ip/obi_spi_slave/obi_spi_slave.vlt file_type: vlt rtl-fpga: @@ -521,3 +523,4 @@ targets: openroad: flow_path: flow/OpenROAD-flow-scripts/flow make_target: synth + diff --git a/docs/source/Peripherals/OBI SPI slave.md b/docs/source/Peripherals/OBI SPI slave.md new file mode 100644 index 000000000..b4618c6a9 --- /dev/null +++ b/docs/source/Peripherals/OBI SPI slave.md @@ -0,0 +1,514 @@ +# OBI SPI slave + + +```{contents} Table of Contents +:depth: 2 +``` + +## Preliminary Definitions + +### Master and Host + +The terms _Master_ and _Host_ are used interchangeably. + +### Slave and Target + +The terms _Slave_ and _Target_ are used interchangeably. + +### SPI Target + +The term _SPI Target_ refers to the SPI target of the _OBI SPI slave IP_. + +### TX and RX + +- TX: Transmission-related data or operations. + +- RX: Reception-related data or operations. + +This is relative depending on the device used. E.g. when a _SPI Host_ sends data to a _SPI Target_ it is a TX transmission for the _SPI Host_ and a RX transmission for the _SPI Target_. + +### Word + +A word, when used as a unit of digital information, always refers to 32 bits +throughout the entire documentation. + +### Command + +A command is a 8-bit long instruction sent by the _SPI Host_ to the _SPI Target_. + +### Wrap length + +The wrap length describes the amount of words that are to be written or read. + +### Dummy cycles + +The dummy cycles describe the amount of clock cycles the _SPI Target_ waits until it sends its data to the _SPI Host_. + +### IP + +IP is short for integrated peripheral. In this context IP means that the peripheral has been integrated into X-HEEP. + + +## Introduction + +The _OBI SPI slave IP_ is connected to X-HEEP's OBI bus and enables the flow of data from the OBI bus to an external _SPI Host_. This documentation uses the code from the _example_spi_obi_slave_ which uses the already into X-HEEPs integrated _SPI Host_ to demonstrate how the communication with the _OBI SPI slave IP_ works. It is important to note that the _example_spi_obi_slave_ code uses the current library from the _SPI Host IP_ which means that it potentially could be outdated and improved. + +## Configurations + +### Chip select + +The _SPI Host_ has to activate the active low chip select signal for every kind of communication with the _SPI Target_. + + +### SPI target registers + +The _SPI Target_ of the _OBI SPI slave IP_ contains three different 8-bit registers which store the values for the dummy cycles and wrap length features. To configurate these registers one has to send a read or write command byte to the _SPI Target_ to specify the desired register. After the command byte has been sent the desired 8-bit value can follow. The _OBI SPI slave IP_ processes data with the most significant bit (MSB) first. + + +- `reg0`: This register stores the amount of dummy cycles. `0x11` is the command to write to it and `0x07` is the command to read from it. The default value is `32`. +- `reg1`: This register stores the lower byte of the wrap length. `0x20` is the command to write to it and `0x21` is the command to read from it. The default value is `0`. +- `reg2`: This register stores the upper byte of the wrap length. `0x30` is the command to write to it and `0x31` is the command to read from it. The default value is `0`. + +```{note} +The values of the registers will return to their default values when reset signal from the OBI bus is active. +``` + +### Reading and writing + +To read from the _OBI SPI slave IP_ the _SPI Host_ has to send the read command which is `0x0B`. After the read command has been sent it has to be followed by the 32-bit starting address of the desired data. + +To write from the _SPI Host_ to the _SPI Target_ the _SPI Host_ has to send the write command which is `0x02`. After the write command has been sent it has to be followed by the 32-bit address of the desired data destination. + +## Writing Data + +### Process + +The process of writing data from a _SPI Host_ to the _SPI Target_ is as follows: + +1. Activate the the active low chip select +1. Define the amount of words that are to be sent. This is done by sending the command byte to write into `reg1` followed by the lower byte of the wrap length. This is followed up with the command byte to write into `reg2` and the upper wrap length. +1. Send the write command to the _SPI Target_ (which is `0x02`). +1. Send the address where the data is to be stored. This has to be a 32-bit address. +1. Send the data. The data can only be sent in words. +1. Deactivate the chip select. + +### Example with SPI Host IP + +In the code examples the explanation of the initial configuration of the _SPI Host IP_ is omitted. It is, however, required when using the integrated _SPI Host IP_. The code example follows the proccess defined above and adapts it to the _SPI Host IP_. + +This function handles the entire writing process: + +```c +static spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + /* The OBI SPI slave IP has no way of knowing or handling incorrect addresses. + * Using an incorrect address leads to an abort of the simulation and it is + * unkown what happens when this issue occurs after physical implementation. + * This is why it is absolutely necessary to include checks such as the following two lines. + */ + if(DIV_ROUND_UP(length, WORD_SIZE_IN_BYTES) > MAX_DATA_SIZE) return SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED; + if (addr % 4 != 0 || (addr + length) > LAST_VALID_ADDRESS) return SPI_SLAVE_FLAG_ADDRESS_INVALID; + + + spi_slave_write_wrap_length(length); + + spi_write_byte(spi_hst, WRITE_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + ///write address + spi_write_word(spi_hst, make_word_compatible_for_spi_host(addr)); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); + + + /* + * Place data in TX FIFO + * In simulation it do not wait for the flash to be ready, so we must check + * if the FIFO is full before writing. + */ + uint16_t counter = 0; + uint32_t *data_32bit = (uint32_t *)data; + for (uint16_t i = 0; i < (length>>2 ); i++) { + if(SPI_HOST_PARAM_TX_DEPTH == counter){ + send_command_to_spi_host(SPI_HOST_PARAM_TX_DEPTH*WORD_SIZE_IN_BYTES, true, SPI_DIR_TX_ONLY); + counter = 0; + } + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(data_32bit[i])); + counter++; + } + if (length % 4 != 0) { + uint32_t last_word = 0; + memcpy(&last_word, &data[length - length % 4], length % 4); + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(last_word)); + } + + uint16_t last_words = length-((length/(SPI_HOST_PARAM_TX_DEPTH*4))*(SPI_HOST_PARAM_TX_DEPTH*4)); + + if(last_words == 0){ + last_words = SPI_HOST_PARAM_TX_DEPTH*4; + } + + send_command_to_spi_host(last_words, false, SPI_DIR_TX_ONLY); + return SPI_FLAG_SUCCESS; // Success +} +``` + +#### Activate the the active low chip select +The _SPI Host IP_ automatically activates the chip select when a command is sent. It has to be specified if the chip select should remain active after sending a command to ensure deactivation. + +The helper function `send_command_to_spi_host` is used to create and send commands to the SPI Host: + + +```c +static void send_command_to_spi_host(uint32_t len, bool csaat, uint8_t direction){ + if(direction != SPI_DIR_DUMMY){ + len--; //The SPI HOST IP uses len-1 = amount of bytes to read and write. But also len = amount of dummy cycles + } + const uint32_t send_cmd_byte = spi_create_command((spi_command_t){ + .len = len, + .csaat = csaat, // Command not finished e.g. CS remains low after transaction + .speed = SPI_SPEED_STANDARD, // Single speed + .direction = direction + }); + spi_set_command(spi_hst, send_cmd_byte); + spi_wait_for_ready(spi_hst); +} +``` + + +#### Define the amount of words that are to be sent + +The length (wrap length) must be set by writing to `reg1` (lower byte) and `reg2` (upper byte). The following function handles this: +```c +void spi_slave_write_wrap_length(uint16_t length){ + + uint32_t wrap_length_cmds = (WRITE_SPI_SLAVE_REG_1 << 24) + + (LOWER_BYTE_16_BITS(length>>2) << 16) //Wraplength low + + (WRITE_SPI_SLAVE_REG_2 << 8) + + (UPPER_BYTE_16_BITS(length>>2)); //Wraplength high + + spi_write_word(spi_hst, make_word_compatible_for_spi_host(wrap_length_cmds)); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); +} +``` + +The _SPI Host IP_ sends an individual byte in the most significant bit (MSB) first order. However, when the _SPI Host IP_ sends out more than one byte or a word, the byte order gets changed to send out the least significant byte first. To correct this the following helper function has been added: + +```c +uint32_t make_word_compatible_for_spi_host(uint32_t word){ + return (LOWER_BYTE_16_BITS(LOWER_BYTES_32_BITS(word)) << 24) + | (UPPER_BYTE_16_BITS(LOWER_BYTES_32_BITS(word)) << 16) + | (LOWER_BYTE_16_BITS(UPPER_BYTES_32_BITS(word)) << 8) + | UPPER_BYTE_16_BITS(UPPER_BYTES_32_BITS(word)); +} +``` +#### Send the write command to the SPI Target + +The write command (`0x02`) is sent to the SPI Target to initiate the data transfer. + +```c +spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + + ... + + spi_write_byte(spi_hst, WRITE_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + ... +} +``` + +#### Send the address where the data is to be stored + +The code includes checks to make sure that the chosen address is correct and that the amount of data doesn't overflow the memory. This is important since the _OBI SPI slave IP_ has no way of indicating invalid addresses or memory overflows of X-HEEPs RAM. Also it is unknown how X-HEEPs OBI bus handles the access of invalid addresses since the simulation aborts in that case. Thus this has to be prevented in the code of the SPI Host. + +```c +spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + if(DIV_ROUND_UP(length, WORD_SIZE_IN_BYTES) > MAX_DATA_SIZE) return SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED; + if (addr % 4 != 0 || (addr + length) > LAST_VALID_ADDRESS) return SPI_SLAVE_FLAG_ADDRESS_INVALID; + + ... + + spi_write_word(spi_hst, make_word_compatible_for_spi_host(addr)); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); + + + ... +} +``` + +#### Send the data + +The data is sent word by word. If the length exceeds the TX FIFO depth of the _SPI Host IP_, the process ensures the FIFO is flushed in chunks. + + +```c +static spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + + ... + + /* + * Place data in TX FIFO + * In simulation it do not wait for the flash to be ready, so we must check + * if the FIFO is full before writing. + */ + uint16_t counter = 0; + uint32_t *data_32bit = (uint32_t *)data; + for (uint16_t i = 0; i < (length>>2 ); i++) { + if(SPI_HOST_PARAM_TX_DEPTH == counter){ + send_command_to_spi_host(SPI_HOST_PARAM_TX_DEPTH*WORD_SIZE_IN_BYTES, true, SPI_DIR_TX_ONLY); + counter = 0; + } + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(data_32bit[i])); + counter++; + } + if (length % 4 != 0) { + uint32_t last_word = 0; + memcpy(&last_word, &data[length - length % 4], length % 4); + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(last_word)); + } + + uint16_t last_words = length-((length/(SPI_HOST_PARAM_TX_DEPTH*4))*(SPI_HOST_PARAM_TX_DEPTH*4)); + + if(last_words == 0){ + last_words = SPI_HOST_PARAM_TX_DEPTH*4; + } + + send_command_to_spi_host(last_words, false, SPI_DIR_TX_ONLY); + return SPI_FLAG_SUCCESS; +} +``` +#### Deactivate the chip select + +As stated before every command includes the option of deactivating the chip select after transmission which has been done. + +```c +static spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + + ... + + send_command_to_spi_host(last_words, false, SPI_DIR_TX_ONLY); + return SPI_FLAG_SUCCESS; +} +``` + + +## Reading Data + +### Process + +The process of reading data from the _SPI Target_ with a_SPI Host_ is as follows: + +1. Activate the the active low chip select +1. (Optional) Set the amount of dummy cycles. This is done by writing to `reg` with the command `0x11` which is followed up by the desired dummy cycle value. This is a 8-bit value. By default the dummy cycles are 32 when not changed previously. +1. Define the amount of words that are to be read. This is done by sending the command byte to write into `reg1` followed by the lower byte of the wrap length. This is followed up with the command byte to write into `reg2` and the upper wrap length. +1. Send the read command to the _SPI Target_ (which is `0x0B`). +1. Send the starting address where the data is stored. This has to be a 32-bit address. +1. Wait the amount of dummy cycles. +1. Read the data. The data can only be read in words. +1. Deactivate the chip select. + +```{caution} +The amount of dummy cycles can't be too low since the communication between the _OBI SPI slave IP_ and the OBI Bus doesn't fill the TX FIFO buffer instantly. Thus setting the dummy cycles too low would lead the _SPI Host_ to read from the _SPI Target_ when the FIFO buffer of the _SPI Target_ isn't ready. This would result in the _SPI Host_ reading zeroes instead of the real data and it would desynchronize the _SPI Host_ and _SPI Target_. +``` +### Example with SPI Host IP + +```c +spi_flags_e spi_slave_read(uint32_t addr, void* data, uint16_t length, uint8_t dummy_cycles){ + if(DIV_ROUND_UP(length, WORD_SIZE_IN_BYTES) > MAX_DATA_SIZE) return SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED; + if (addr % 4 != 0 || (addr + length) > LAST_VALID_ADDRESS) return SPI_SLAVE_FLAG_ADDRESS_INVALID; + + + spi_slave_write_dummy_cycles(dummy_cycles); + + spi_slave_write_wrap_length(length); + + + spi_write_byte(spi_hst, READ_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + spi_write_word(spi_hst, make_word_compatible_for_spi_host(addr)); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); + + spi_host_wait(dummy_cycles); + + send_command_to_spi_host(length, false, SPI_DIR_RX_ONLY); + + + /* + * Set RX watermark to length. The watermark is in words. + * If the length is not a multiple of 4, the RX watermark is set to length/4+1 + * to take into account the extra bytes. + * If the length is higher then the RX FIFO depth, the RX watermark is set to + * RX FIFO depth. In this case the flag is not set to 0, so the loop will + * continue until all the data is read. + */ + bool flag = 1; + uint16_t to_read = 0; + uint16_t i_start = 0; + uint16_t length_original = length; + uint32_t *data_32bit = (uint32_t *)data; + while (flag) { + if (length >= SPI_HOST_PARAM_RX_DEPTH) { + spi_set_rx_watermark(spi_hst, SPI_HOST_PARAM_RX_DEPTH>>2); + length -= SPI_HOST_PARAM_RX_DEPTH; + to_read += SPI_HOST_PARAM_RX_DEPTH; + } + else { + spi_set_rx_watermark(spi_hst, (length%4==0 ? length>>2 : (length>>2)+1)); + to_read += length; + flag = 0; + } + // Wait till SPI Host RX FIFO is full (or I read all the data) + spi_wait_for_rx_watermark(spi_hst); + // Read data from SPI Host RX FIFO + for (uint16_t i = i_start; i < to_read>>2; i++) { + spi_read_word(spi_hst, &data_32bit[i]); // Writes a full word + } + // Update the starting index + i_start += SPI_HOST_PARAM_RX_DEPTH>>2; + } + // Take into account the extra bytes (if any) + if (length_original % 4 != 0) { + uint32_t last_word = 0; + spi_read_word(spi_hst, &last_word); + memcpy(&data_32bit[length_original>>2], &last_word, length%4); + } + + return SPI_FLAG_SUCCESS; // Success +} +``` +#### Activate the the active low chip select +Similar to the write operation, the SPI Host automatically activates the chip select when commands are sent. + + + +#### (Optional) Set the amount of dummy cycles +Dummy cycles are set by writing to `reg0`. By default, 32 cycles are used if not explicitly set. + + +```c +void spi_slave_write_dummy_cycles(uint8_t cycles){ + + // Load command to TX FIFO + spi_write_byte(spi_hst, WRITE_SPI_SLAVE_REG_0); + spi_wait_for_ready(spi_hst); + + //The spi_write_byte function fills always the same byte in the TX FIFO of the SPI HOST IP + //Hence it is not possible to write the reg 1 command and cycles at the same time without writing a word. + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + // Load command to TX FIFO + spi_write_byte(spi_hst, cycles); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); +} +``` + +#### Define the amount of words that are to be read +This is handled the same way as in the write operation. + +#### Send the read command to the SPI Target + +The read command (`0x0B`) is sent to the SPI Target: + +```c +spi_flags_e spi_slave_read(uint32_t addr, void* data, uint16_t length, uint8_t dummy_cycles){ + + ... + + spi_write_byte(spi_hst, READ_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); +} +``` + +#### Send the starting address where the data is stored +Same as in the writing example. + +#### Wait the amount of dummy cycles + +The SPI Host has to send the amount of dummy cycles as clock cycles to the _OBI SPI slave IP_. This functionality is already integrated in the _SPI Host IP_. + +```c +void spi_host_wait(uint8_t cycles){ + if(cycles == 0){ + return; + } + send_command_to_spi_host(cycles, true, SPI_DIR_DUMMY); +} +``` + +#### Read the data + +```c +spi_flags_e spi_slave_read(uint32_t addr, void* data, uint16_t length, uint8_t dummy_cycles){ + + ... + + /* + * Set RX watermark to length. The watermark is in words. + * If the length is not a multiple of 4, the RX watermark is set to length/4+1 + * to take into account the extra bytes. + * If the length is higher then the RX FIFO depth, the RX watermark is set to + * RX FIFO depth. In this case the flag is not set to 0, so the loop will + * continue until all the data is read. + */ + bool flag = 1; + uint16_t to_read = 0; + uint16_t i_start = 0; + uint16_t length_original = length; + uint32_t *data_32bit = (uint32_t *)data; + while (flag) { + if (length >= SPI_HOST_PARAM_RX_DEPTH) { + spi_set_rx_watermark(spi_hst, SPI_HOST_PARAM_RX_DEPTH>>2); + length -= SPI_HOST_PARAM_RX_DEPTH; + to_read += SPI_HOST_PARAM_RX_DEPTH; + } + else { + spi_set_rx_watermark(spi_hst, (length%4==0 ? length>>2 : (length>>2)+1)); + to_read += length; + flag = 0; + } + // Wait till SPI Host RX FIFO is full (or I read all the data) + spi_wait_for_rx_watermark(spi_hst); + // Read data from SPI Host RX FIFO + for (uint16_t i = i_start; i < to_read>>2; i++) { + spi_read_word(spi_hst, &data_32bit[i]); // Writes a full word + } + // Update the starting index + i_start += SPI_HOST_PARAM_RX_DEPTH>>2; + } + // Take into account the extra bytes (if any) + if (length_original % 4 != 0) { + uint32_t last_word = 0; + spi_read_word(spi_hst, &last_word); + memcpy(&data_32bit[length_original>>2], &last_word, length%4); + } + + return SPI_FLAG_SUCCESS; // Success +} +``` + +#### Deactivate the chip select +Same as in the writing example. + + +## Remarks for writing a SPI Host code + +### Address handeling + +The _OBI SPI slave IP_ has no way of knowing or handling incorrect addresses. Using an incorrect address leads to an abort of the simulation and it is unkown what happens when this issue occurs after physical implementation. This is why it is absolutely necessary that the SPI Host handles the addresses properly. \ No newline at end of file diff --git a/hw/core-v-mini-mcu/ao_peripheral_subsystem.sv b/hw/core-v-mini-mcu/ao_peripheral_subsystem.sv index f054c37b4..f3eee6288 100644 --- a/hw/core-v-mini-mcu/ao_peripheral_subsystem.sv +++ b/hw/core-v-mini-mcu/ao_peripheral_subsystem.sv @@ -47,6 +47,27 @@ module ao_peripheral_subsystem output logic spi_flash_intr_event_o, + + //SPI Slave + output logic spi_slave_sck_o, + input logic spi_slave_sck_i, + output logic spi_slave_sck_oe_o, + output logic spi_slave_cs_o, + input logic spi_slave_cs_i, + output logic spi_slave_cs_oe_o, + output logic spi_slave_miso_o, + input logic spi_slave_miso_i, + output logic spi_slave_miso_oe_o, + output logic spi_slave_mosi_o, + input logic spi_slave_mosi_i, + output logic spi_slave_mosi_oe_o, + + //OBI master cpu subsystem: + + // Instruction memory interface + output obi_req_t spi_slave_req_o, + input obi_resp_t spi_slave_resp_i, + // POWER MANAGER input logic [31:0] intr_i, input logic [NEXT_INT_RND-1:0] intr_vector_ext_i, @@ -482,4 +503,33 @@ module ao_peripheral_subsystem .intr_rx_parity_err_o(uart_intr_rx_parity_err_o) ); + + obi_spi_slave obi_spi_slave_i ( + .spi_sclk(spi_slave_sck_i), + .spi_cs(spi_slave_cs_i), + .spi_miso(spi_slave_miso_o), + .spi_mosi(spi_slave_mosi_i), + .obi_aclk(clk_i), + .obi_aresetn(rst_ni), + .obi_master_req(spi_slave_req_o.req), + .obi_master_gnt(spi_slave_resp_i.gnt), + .obi_master_addr(spi_slave_req_o.addr), + .obi_master_we(spi_slave_req_o.we), + .obi_master_w_data(spi_slave_req_o.wdata), + .obi_master_be(spi_slave_req_o.be), + .obi_master_r_valid(spi_slave_resp_i.rvalid), + .obi_master_r_data(spi_slave_resp_i.rdata) + ); + + + + assign spi_slave_sck_o = 1'b0; + assign spi_slave_sck_oe_o = 1'b0; + assign spi_slave_cs_o = 1'b0; + assign spi_slave_cs_oe_o = 1'b0; + assign spi_slave_miso_oe_o = 1'b1; + assign spi_slave_mosi_o = 1'b0; + assign spi_slave_mosi_oe_o = 1'b0; + + endmodule : ao_peripheral_subsystem diff --git a/hw/core-v-mini-mcu/core_v_mini_mcu.sv b/hw/core-v-mini-mcu/core_v_mini_mcu.sv index 3ebc1e77f..1592f4a3d 100644 --- a/hw/core-v-mini-mcu/core_v_mini_mcu.sv +++ b/hw/core-v-mini-mcu/core_v_mini_mcu.sv @@ -102,22 +102,6 @@ module core_v_mini_mcu input logic gpio_13_i, output logic gpio_13_oe_o, - output logic gpio_14_o, - input logic gpio_14_i, - output logic gpio_14_oe_o, - - output logic gpio_15_o, - input logic gpio_15_i, - output logic gpio_15_oe_o, - - output logic gpio_16_o, - input logic gpio_16_i, - output logic gpio_16_oe_o, - - output logic gpio_17_o, - input logic gpio_17_i, - output logic gpio_17_oe_o, - output logic spi_flash_sck_o, input logic spi_flash_sck_i, output logic spi_flash_sck_oe_o, @@ -174,6 +158,34 @@ module core_v_mini_mcu input logic spi_sd_3_i, output logic spi_sd_3_oe_o, + output logic spi_slave_sck_o, + input logic spi_slave_sck_i, + output logic spi_slave_sck_oe_o, + output logic gpio_14_o, + input logic gpio_14_i, + output logic gpio_14_oe_o, + + output logic spi_slave_cs_o, + input logic spi_slave_cs_i, + output logic spi_slave_cs_oe_o, + output logic gpio_15_o, + input logic gpio_15_i, + output logic gpio_15_oe_o, + + output logic spi_slave_miso_o, + input logic spi_slave_miso_i, + output logic spi_slave_miso_oe_o, + output logic gpio_16_o, + input logic gpio_16_i, + output logic gpio_16_oe_o, + + output logic spi_slave_mosi_o, + input logic spi_slave_mosi_i, + output logic spi_slave_mosi_oe_o, + output logic gpio_17_o, + input logic gpio_17_i, + output logic gpio_17_oe_o, + output logic pdm2pcm_pdm_o, input logic pdm2pcm_pdm_i, output logic pdm2pcm_pdm_oe_o, @@ -383,6 +395,10 @@ module core_v_mini_mcu obi_req_t peripheral_slave_req; obi_resp_t peripheral_slave_resp; + //OBI SPI slave + obi_req_t spi_slave_req; + obi_resp_t spi_slave_resp; + // signals to debug unit logic debug_core_req; logic debug_reset_n; @@ -459,6 +475,90 @@ module core_v_mini_mcu assign memory_subsystem_banks_powergate_iso_n[1] = memory_subsystem_pwr_ctrl_out[1].isogate_en_n; assign memory_subsystem_banks_set_retentive_n[1] = memory_subsystem_pwr_ctrl_out[1].retentive_en_n; assign memory_subsystem_clkgate_en_n[1] = memory_subsystem_pwr_ctrl_out[1].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[2] = memory_subsystem_pwr_ctrl_out[2].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[2].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[2]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[2] = memory_subsystem_pwr_ctrl_out[2].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[2] = memory_subsystem_pwr_ctrl_out[2].retentive_en_n; + assign memory_subsystem_clkgate_en_n[2] = memory_subsystem_pwr_ctrl_out[2].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[3] = memory_subsystem_pwr_ctrl_out[3].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[3].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[3]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[3] = memory_subsystem_pwr_ctrl_out[3].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[3] = memory_subsystem_pwr_ctrl_out[3].retentive_en_n; + assign memory_subsystem_clkgate_en_n[3] = memory_subsystem_pwr_ctrl_out[3].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[4] = memory_subsystem_pwr_ctrl_out[4].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[4].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[4]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[4] = memory_subsystem_pwr_ctrl_out[4].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[4] = memory_subsystem_pwr_ctrl_out[4].retentive_en_n; + assign memory_subsystem_clkgate_en_n[4] = memory_subsystem_pwr_ctrl_out[4].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[5] = memory_subsystem_pwr_ctrl_out[5].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[5].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[5]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[5] = memory_subsystem_pwr_ctrl_out[5].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[5] = memory_subsystem_pwr_ctrl_out[5].retentive_en_n; + assign memory_subsystem_clkgate_en_n[5] = memory_subsystem_pwr_ctrl_out[5].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[6] = memory_subsystem_pwr_ctrl_out[6].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[6].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[6]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[6] = memory_subsystem_pwr_ctrl_out[6].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[6] = memory_subsystem_pwr_ctrl_out[6].retentive_en_n; + assign memory_subsystem_clkgate_en_n[6] = memory_subsystem_pwr_ctrl_out[6].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[7] = memory_subsystem_pwr_ctrl_out[7].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[7].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[7]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[7] = memory_subsystem_pwr_ctrl_out[7].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[7] = memory_subsystem_pwr_ctrl_out[7].retentive_en_n; + assign memory_subsystem_clkgate_en_n[7] = memory_subsystem_pwr_ctrl_out[7].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[8] = memory_subsystem_pwr_ctrl_out[8].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[8].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[8]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[8] = memory_subsystem_pwr_ctrl_out[8].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[8] = memory_subsystem_pwr_ctrl_out[8].retentive_en_n; + assign memory_subsystem_clkgate_en_n[8] = memory_subsystem_pwr_ctrl_out[8].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[9] = memory_subsystem_pwr_ctrl_out[9].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[9].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[9]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[9] = memory_subsystem_pwr_ctrl_out[9].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[9] = memory_subsystem_pwr_ctrl_out[9].retentive_en_n; + assign memory_subsystem_clkgate_en_n[9] = memory_subsystem_pwr_ctrl_out[9].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[10] = memory_subsystem_pwr_ctrl_out[10].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[10].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[10]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[10] = memory_subsystem_pwr_ctrl_out[10].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[10] = memory_subsystem_pwr_ctrl_out[10].retentive_en_n; + assign memory_subsystem_clkgate_en_n[10] = memory_subsystem_pwr_ctrl_out[10].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[11] = memory_subsystem_pwr_ctrl_out[11].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[11].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[11]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[11] = memory_subsystem_pwr_ctrl_out[11].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[11] = memory_subsystem_pwr_ctrl_out[11].retentive_en_n; + assign memory_subsystem_clkgate_en_n[11] = memory_subsystem_pwr_ctrl_out[11].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[12] = memory_subsystem_pwr_ctrl_out[12].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[12].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[12]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[12] = memory_subsystem_pwr_ctrl_out[12].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[12] = memory_subsystem_pwr_ctrl_out[12].retentive_en_n; + assign memory_subsystem_clkgate_en_n[12] = memory_subsystem_pwr_ctrl_out[12].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[13] = memory_subsystem_pwr_ctrl_out[13].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[13].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[13]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[13] = memory_subsystem_pwr_ctrl_out[13].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[13] = memory_subsystem_pwr_ctrl_out[13].retentive_en_n; + assign memory_subsystem_clkgate_en_n[13] = memory_subsystem_pwr_ctrl_out[13].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[14] = memory_subsystem_pwr_ctrl_out[14].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[14].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[14]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[14] = memory_subsystem_pwr_ctrl_out[14].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[14] = memory_subsystem_pwr_ctrl_out[14].retentive_en_n; + assign memory_subsystem_clkgate_en_n[14] = memory_subsystem_pwr_ctrl_out[14].clkgate_en_n; + assign memory_subsystem_banks_powergate_switch_n[15] = memory_subsystem_pwr_ctrl_out[15].pwrgate_en_n; + assign memory_subsystem_pwr_ctrl_in[15].pwrgate_ack_n = memory_subsystem_banks_powergate_switch_ack_n[15]; + //isogate exposed outside for UPF sim flow and switch cells + assign memory_subsystem_banks_powergate_iso_n[15] = memory_subsystem_pwr_ctrl_out[15].isogate_en_n; + assign memory_subsystem_banks_set_retentive_n[15] = memory_subsystem_pwr_ctrl_out[15].retentive_en_n; + assign memory_subsystem_clkgate_en_n[15] = memory_subsystem_pwr_ctrl_out[15].clkgate_en_n; for (genvar i = 0; i < EXT_DOMAINS_RND; i = i + 1) begin assign external_subsystem_powergate_switch_no[i] = external_subsystem_pwr_ctrl_out[i].pwrgate_en_n; @@ -604,7 +704,9 @@ module core_v_mini_mcu .ext_dma_write_req_o(ext_dma_write_req_o), .ext_dma_write_resp_i(ext_dma_write_resp_i), .ext_dma_addr_req_o(ext_dma_addr_req_o), - .ext_dma_addr_resp_i(ext_dma_addr_resp_i) + .ext_dma_addr_resp_i(ext_dma_addr_resp_i), + .spi_slave_req_i(spi_slave_req), + .spi_slave_resp_o(spi_slave_resp) ); memory_subsystem #( @@ -692,7 +794,21 @@ module core_v_mini_mcu .ext_dma_slot_tx_i, .ext_dma_slot_rx_i, .ext_dma_stop_i, - .dma_done_o + .dma_done_o, + .spi_slave_sck_o(spi_slave_sck_o), + .spi_slave_sck_i(spi_slave_sck_i), + .spi_slave_sck_oe_o(spi_slave_sck_oe_o), + .spi_slave_cs_o(spi_slave_cs_o), + .spi_slave_cs_i(spi_slave_cs_i), + .spi_slave_cs_oe_o(spi_slave_cs_oe_o), + .spi_slave_miso_o(spi_slave_miso_o), + .spi_slave_miso_i(spi_slave_miso_i), + .spi_slave_miso_oe_o(spi_slave_miso_oe_o), + .spi_slave_mosi_o(spi_slave_mosi_o), + .spi_slave_mosi_i(spi_slave_mosi_i), + .spi_slave_mosi_oe_o(spi_slave_mosi_oe_o), + .spi_slave_req_o(spi_slave_req), + .spi_slave_resp_i(spi_slave_resp) ); peripheral_subsystem peripheral_subsystem_i ( diff --git a/hw/core-v-mini-mcu/core_v_mini_mcu.sv.tpl b/hw/core-v-mini-mcu/core_v_mini_mcu.sv.tpl index aedf2e2c3..ad57c2ec8 100644 --- a/hw/core-v-mini-mcu/core_v_mini_mcu.sv.tpl +++ b/hw/core-v-mini-mcu/core_v_mini_mcu.sv.tpl @@ -137,6 +137,10 @@ ${pad.core_v_mini_mcu_interface} obi_req_t peripheral_slave_req; obi_resp_t peripheral_slave_resp; + //OBI SPI slave + obi_req_t spi_slave_req; + obi_resp_t spi_slave_resp; + // signals to debug unit logic debug_core_req; logic debug_reset_n; @@ -354,7 +358,9 @@ ${pad.core_v_mini_mcu_interface} .ext_dma_write_req_o(ext_dma_write_req_o), .ext_dma_write_resp_i(ext_dma_write_resp_i), .ext_dma_addr_req_o(ext_dma_addr_req_o), - .ext_dma_addr_resp_i(ext_dma_addr_resp_i) + .ext_dma_addr_resp_i(ext_dma_addr_resp_i), + .spi_slave_req_i(spi_slave_req), + .spi_slave_resp_o(spi_slave_resp) ); memory_subsystem #( @@ -440,7 +446,21 @@ ${pad.core_v_mini_mcu_interface} .ext_dma_slot_tx_i, .ext_dma_slot_rx_i, .ext_dma_stop_i, - .dma_done_o + .dma_done_o, + .spi_slave_sck_o(spi_slave_sck_o), + .spi_slave_sck_i(spi_slave_sck_i), + .spi_slave_sck_oe_o(spi_slave_sck_oe_o), + .spi_slave_cs_o(spi_slave_cs_o), + .spi_slave_cs_i(spi_slave_cs_i), + .spi_slave_cs_oe_o(spi_slave_cs_oe_o), + .spi_slave_miso_o(spi_slave_miso_o), + .spi_slave_miso_i(spi_slave_miso_i), + .spi_slave_miso_oe_o(spi_slave_miso_oe_o), + .spi_slave_mosi_o(spi_slave_mosi_o), + .spi_slave_mosi_i(spi_slave_mosi_i), + .spi_slave_mosi_oe_o(spi_slave_mosi_oe_o), + .spi_slave_req_o(spi_slave_req), + .spi_slave_resp_i(spi_slave_resp) ); peripheral_subsystem peripheral_subsystem_i ( diff --git a/hw/core-v-mini-mcu/include/core_v_mini_mcu_pkg.sv.tpl b/hw/core-v-mini-mcu/include/core_v_mini_mcu_pkg.sv.tpl index 00ffc0257..bc05043fc 100644 --- a/hw/core-v-mini-mcu/include/core_v_mini_mcu_pkg.sv.tpl +++ b/hw/core-v-mini-mcu/include/core_v_mini_mcu_pkg.sv.tpl @@ -38,11 +38,14 @@ package core_v_mini_mcu_pkg; localparam logic [31:0] CORE_INSTR_IDX = 0; localparam logic [31:0] CORE_DATA_IDX = 1; localparam logic [31:0] DEBUG_MASTER_IDX = 2; - localparam logic [31:0] DMA_READ_P0_IDX = 3; - localparam logic [31:0] DMA_WRITE_P0_IDX = 4; - localparam logic [31:0] DMA_ADDR_P0_IDX = 5; + localparam logic [31:0] SPI_SLAVE_IDX = 3; + localparam logic [31:0] DMA_READ_P0_IDX = 4; + localparam logic [31:0] DMA_WRITE_P0_IDX = 5; + localparam logic [31:0] DMA_ADDR_P0_IDX = 6; + - localparam SYSTEM_XBAR_NMASTER = ${3 + int(num_dma_master_ports)*3}; + + localparam SYSTEM_XBAR_NMASTER = ${4 + int(num_dma_master_ports)*3}; // Internal slave memory map and index // ----------------------------------- diff --git a/hw/core-v-mini-mcu/peripheral_subsystem.sv b/hw/core-v-mini-mcu/peripheral_subsystem.sv index 4fa663e66..69d5e4918 100644 --- a/hw/core-v-mini-mcu/peripheral_subsystem.sv +++ b/hw/core-v-mini-mcu/peripheral_subsystem.sv @@ -70,6 +70,7 @@ module peripheral_subsystem output logic [ 3:0] spi2_sd_en_o, input logic [ 3:0] spi2_sd_i, + //RV TIMER output logic rv_timer_2_intr_o, output logic rv_timer_3_intr_o, @@ -443,8 +444,17 @@ module peripheral_subsystem .intr_spi_event_o(spi2_intr_event) ); - assign peripheral_slv_rsp[core_v_mini_mcu_pkg::PDM2PCM_IDX] = '0; - assign pdm2pcm_clk_o = '0; + pdm2pcm #( + .reg_req_t(reg_pkg::reg_req_t), + .reg_rsp_t(reg_pkg::reg_rsp_t) + ) pdm2pcm_i ( + .clk_i(clk_cg), + .rst_ni, + .reg_req_i(peripheral_slv_req[core_v_mini_mcu_pkg::PDM2PCM_IDX]), + .reg_rsp_o(peripheral_slv_rsp[core_v_mini_mcu_pkg::PDM2PCM_IDX]), + .pdm_i(pdm2pcm_pdm_i), + .pdm_clk_o(pdm2pcm_clk_o) + ); assign pdm2pcm_clk_en_o = 1; @@ -470,4 +480,6 @@ module peripheral_subsystem .i2s_rx_valid_o(i2s_rx_valid_o) ); + + endmodule : peripheral_subsystem diff --git a/hw/core-v-mini-mcu/peripheral_subsystem.sv.tpl b/hw/core-v-mini-mcu/peripheral_subsystem.sv.tpl index 4025cd88a..7f7bd3eff 100644 --- a/hw/core-v-mini-mcu/peripheral_subsystem.sv.tpl +++ b/hw/core-v-mini-mcu/peripheral_subsystem.sv.tpl @@ -70,6 +70,7 @@ module peripheral_subsystem output logic [ 3:0] spi2_sd_en_o, input logic [ 3:0] spi2_sd_i, + //RV TIMER output logic rv_timer_2_intr_o, output logic rv_timer_3_intr_o, @@ -601,4 +602,6 @@ module peripheral_subsystem % endif % endfor + + endmodule : peripheral_subsystem diff --git a/hw/core-v-mini-mcu/system_bus.sv.tpl b/hw/core-v-mini-mcu/system_bus.sv.tpl index 221ec067e..18728b99d 100644 --- a/hw/core-v-mini-mcu/system_bus.sv.tpl +++ b/hw/core-v-mini-mcu/system_bus.sv.tpl @@ -37,6 +37,9 @@ module system_bus input obi_req_t debug_master_req_i, output obi_resp_t debug_master_resp_o, + input obi_req_t spi_slave_req_i, + output obi_resp_t spi_slave_resp_o, + input obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] dma_read_req_i, output obi_resp_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] dma_read_resp_o, @@ -60,6 +63,7 @@ module system_bus output obi_req_t ao_peripheral_slave_req_o, input obi_resp_t ao_peripheral_slave_resp_i, + output obi_req_t peripheral_slave_req_o, input obi_resp_t peripheral_slave_resp_i, @@ -119,11 +123,12 @@ module system_bus assign int_master_req[core_v_mini_mcu_pkg::CORE_INSTR_IDX] = core_instr_req_i; assign int_master_req[core_v_mini_mcu_pkg::CORE_DATA_IDX] = core_data_req_i; assign int_master_req[core_v_mini_mcu_pkg::DEBUG_MASTER_IDX] = debug_master_req_i; + assign int_master_req[core_v_mini_mcu_pkg::SPI_SLAVE_IDX] = spi_slave_req_i; % for i in range(int(num_dma_master_ports)): - assign int_master_req[${3+i*3}] = dma_read_req_i[${i}]; - assign int_master_req[${4+i*3}] = dma_write_req_i[${i}]; - assign int_master_req[${5+i*3}] = dma_addr_req_i[${i}]; + assign int_master_req[${4+i*3}] = dma_read_req_i[${i}]; + assign int_master_req[${5+i*3}] = dma_write_req_i[${i}]; + assign int_master_req[${6+i*3}] = dma_addr_req_i[${i}]; % endfor // Internal + external master requests @@ -145,11 +150,12 @@ module system_bus assign core_instr_resp_o = int_master_resp[core_v_mini_mcu_pkg::CORE_INSTR_IDX]; assign core_data_resp_o = int_master_resp[core_v_mini_mcu_pkg::CORE_DATA_IDX]; assign debug_master_resp_o = int_master_resp[core_v_mini_mcu_pkg::DEBUG_MASTER_IDX]; + assign spi_slave_resp_o = int_master_resp[core_v_mini_mcu_pkg::SPI_SLAVE_IDX]; % for i in range(int(num_dma_master_ports)): - assign dma_read_resp_o[${i}] = int_master_resp[${3+i*3}]; - assign dma_write_resp_o[${i}] = int_master_resp[${4+i*3}]; - assign dma_addr_resp_o[${i}] = int_master_resp[${5+i*3}]; + assign dma_read_resp_o[${i}] = int_master_resp[${4+i*3}]; + assign dma_write_resp_o[${i}] = int_master_resp[${5+i*3}]; + assign dma_addr_resp_o[${i}] = int_master_resp[${6+i*3}]; % endfor // External master responses diff --git a/hw/ip/obi_spi_slave/LICENSE b/hw/ip/obi_spi_slave/LICENSE new file mode 100644 index 000000000..3a2b87e2d --- /dev/null +++ b/hw/ip/obi_spi_slave/LICENSE @@ -0,0 +1,176 @@ +SOLDERPAD HARDWARE LICENSE version 0.51 + +This license is based closely on the Apache License Version 2.0, but is not +approved or endorsed by the Apache Foundation. A copy of the non-modified +Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. + +As this license is not currently OSI or FSF approved, the Licensor permits any +Work licensed under this License, at the option of the Licensee, to be treated +as licensed under the Apache License Version 2.0 (which is so approved). + +This License is licensed under the terms of this License and in particular +clause 7 below (Disclaimer of Warranties) applies in relation to its use. + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the Rights owner or entity authorized by the Rights owner +that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Rights” means copyright and any similar right including design right (whether +registered or unregistered), semiconductor topography (mask) rights and +database rights (but excluding Patents and Trademarks). + +“Source” form shall mean the preferred form for making modifications, including +but not limited to source code, net lists, board layouts, CAD files, +documentation source, and configuration files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object +code, generated documentation, the instantiation of a hardware design and +conversions to other media types, including intermediate forms such as +bytecodes, FPGA bitstreams, artwork and semiconductor topographies (mask +works). + +“Work” shall mean the work of authorship, whether in Source form or other +Object form, made available under the License, as indicated by a Rights notice +that is included in or attached to the work (an example is provided in the +Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) or physically connect to or interoperate with the interfaces of, the Work +and Derivative Works thereof. + +“Contribution” shall mean any design or work of authorship, including the +original version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor for +inclusion in the Work by the Rights owner or by an individual or Legal Entity +authorized to submit on behalf of the Rights owner. For the purposes of this +definition, “submitted” means any form of electronic, verbal, or written +communication sent to the Licensor or its representatives, including but not +limited to communication on electronic mailing lists, source code control +systems, and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but excluding +communication that is conspicuously marked or otherwise designated in writing +by the Rights owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of License. Subject to the terms and conditions of this License, each +Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable license under the Rights to reproduce, +prepare Derivative Works of, publicly display, publicly perform, sublicense, +and distribute the Work and such Derivative Works in Source or Object form and +do anything in relation to the Work as if the Rights did not exist. + +3. Grant of Patent License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, import, +and otherwise transfer the Work, where such license applies only to those +patent claims licensable by such Contributor that are necessarily infringed by +their Contribution(s) alone or by combination of their Contribution(s) with the +Work to which such Contribution(s) was submitted. If You institute patent +litigation against any entity (including a cross-claim or counterclaim in a +lawsuit) alleging that the Work or a Contribution incorporated within the Work +constitutes direct or contributory patent infringement, then any patent +licenses granted to You under this License for that Work shall terminate as of +the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and in +Source or Object form, provided that You meet the following conditions: + + You must give any other recipients of the Work or Derivative Works a copy + of this License; and + + You must cause any modified files to carry prominent notices stating that + You changed the files; and + + You must retain, in the Source form of any Derivative Works that You + distribute, all copyright, patent, trademark, and attribution notices from + the Source form of the Work, excluding those notices that do not pertain to + any part of the Derivative Works; and + + If the Work includes a “NOTICE” text file as part of its distribution, then + any Derivative Works that You distribute must include a readable copy of + the attribution notices contained within such NOTICE file, excluding those + notices that do not pertain to any part of the Derivative Works, in at + least one of the following places: within a NOTICE text file distributed as + part of the Derivative Works; within the Source form or documentation, if + provided along with the Derivative Works; or, within a display generated by + the Derivative Works, if and wherever such third-party notices normally + appear. The contents of the NOTICE file are for informational purposes only + and do not modify the License. You may add Your own attribution notices + within Derivative Works that You distribute, alongside or as an addendum to + the NOTICE text from the Work, provided that such additional attribution + notices cannot be construed as modifying the License. You may add Your own + copyright statement to Your modifications and may provide additional or + different license terms and conditions for use, reproduction, or + distribution of Your modifications, or for any such Derivative Works as a + whole, provided Your use, reproduction, and distribution of the Work + otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without any +additional terms or conditions. Notwithstanding the above, nothing herein shall +supersede or modify the terms of any separate license agreement you may have +executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in +writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in +tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to in +writing, shall any Contributor be liable to You for damages, including any +direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability to +use the Work (including but not limited to damages for loss of goodwill, work +stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the +possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or +Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such +obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree +to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/hw/ip/obi_spi_slave/README.md b/hw/ip/obi_spi_slave/README.md new file mode 100644 index 000000000..8433e692c --- /dev/null +++ b/hw/ip/obi_spi_slave/README.md @@ -0,0 +1,15 @@ +# OBI SPI Slave + +This is an implementation of a simple SPI slave. +An external microcontroller can use the SPI slave to access the memory of the SoC where this IP is +instantiated. The SPI slave uses the OBI bus to access the memory of the target +SoC. + +It contains dual-clock FIFOs to perform the clock domain crossing from SPI to +the SoC (OBI) domain. + +This IP depends on some PULP common cells like clock muxes, clock gates and +clock inverters. Those can be found in the PULP common cells repository or in +the PULPino RTL sources. The clock domain crossing functionality is reused from +the AXI slice DC component, so make sure you compile the AXI slice DC when +using this IP. diff --git a/hw/ip/obi_spi_slave/obi_spi_slave.core b/hw/ip/obi_spi_slave/obi_spi_slave.core new file mode 100644 index 000000000..0ca5e5603 --- /dev/null +++ b/hw/ip/obi_spi_slave/obi_spi_slave.core @@ -0,0 +1,38 @@ +CAPI=2: + +name: "x-heep:ip:obi_spi_slave" +description: "core-v-mini-mcu SPI2OBI peripheral" + +# Copyright 2021 OpenHW Group +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 + +filesets: + files_rtl: + depend: + - pulp-platform.org::common_cells + files: + - obi_spi_slave.sv + - spi_slave_obi_plug.sv + - spi_slave_cmd_parser.sv + - spi_slave_controller.sv + - spi_slave_dc_fifo.sv + - spi_slave_regs.sv + - spi_slave_rx.sv + - spi_slave_syncro.sv + - spi_slave_tx.sv + file_type: systemVerilogSource + + files_verilator_waiver: + depend: + # common waivers + - lowrisc:lint:common + - lowrisc:lint:comportable + files: + - obi_spi_slave.vlt + file_type: vlt + +targets: + default: + filesets: + - files_rtl diff --git a/hw/ip/obi_spi_slave/obi_spi_slave.sv b/hw/ip/obi_spi_slave/obi_spi_slave.sv new file mode 100644 index 000000000..35315201c --- /dev/null +++ b/hw/ip/obi_spi_slave/obi_spi_slave.sv @@ -0,0 +1,202 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module obi_spi_slave #( + + parameter OBI_ADDR_WIDTH = 32, + parameter OBI_DATA_WIDTH = 32, + parameter DUMMY_CYCLES = 32 +) ( + //input logic test_mode, + input logic spi_sclk, + input logic spi_cs, + input logic spi_mosi, + output logic spi_miso, + + // OBI MASTER + //*************************************** + input logic obi_aclk, + input logic obi_aresetn, + + // ADDRESS CHANNEL + output logic obi_master_req, + input logic obi_master_gnt, + output logic [OBI_ADDR_WIDTH-1:0] obi_master_addr, + output logic obi_master_we, + output logic [OBI_DATA_WIDTH-1:0] obi_master_w_data, + output logic [ 3:0] obi_master_be, + + // RESPONSE CHANNEL + input logic obi_master_r_valid, + input logic [OBI_DATA_WIDTH-1:0] obi_master_r_data +); + + logic [ 7:0] rx_counter; + logic rx_counter_upd; + logic [ 31:0] rx_data; + logic rx_data_valid; + + logic [ 7:0] tx_counter; + logic tx_counter_upd; + logic [ 31:0] tx_data; + logic tx_data_valid; + + logic ctrl_rd_wr; + + logic [ 31:0] ctrl_addr; + logic ctrl_addr_valid; + + logic [ 31:0] ctrl_data_rx; + logic ctrl_data_rx_valid; + logic ctrl_data_rx_ready; + logic [ 31:0] ctrl_data_tx; + logic ctrl_data_tx_valid; + logic ctrl_data_tx_ready; + + logic [ 31:0] fifo_data_rx; + logic fifo_data_rx_valid; + logic fifo_data_rx_ready; + logic [ 31:0] fifo_data_tx; + logic fifo_data_tx_valid; + logic fifo_data_tx_ready; + + logic [OBI_ADDR_WIDTH-1:0] addr_sync; + logic addr_valid_sync; + logic cs_sync; + + logic tx_done; + logic rd_wr_sync; + + logic [ 15:0] wrap_length; + logic test_mode; + + spi_slave_rx u_rxreg ( + .sclk (spi_sclk), + .cs (spi_cs), + .mosi (spi_mosi), + .counter_in (rx_counter), + .counter_in_upd(rx_counter_upd), + .data (rx_data), + .data_ready (rx_data_valid) + ); + + spi_slave_tx u_txreg ( + .test_mode (test_mode), + .sclk (spi_sclk), + .cs (spi_cs), + .miso (spi_miso), + .counter_in (tx_counter), + .counter_in_upd(tx_counter_upd), + .data (tx_data), + .data_valid (tx_data_valid), + .done (tx_done) + ); + + spi_slave_controller #( + .DUMMY_CYCLES(DUMMY_CYCLES) + ) u_slave_sm ( + .sclk (spi_sclk), + .sys_rstn (obi_aresetn), + .cs (spi_cs), + .rx_counter (rx_counter), + .rx_counter_upd (rx_counter_upd), + .rx_data (rx_data), + .rx_data_valid (rx_data_valid), + .tx_counter (tx_counter), + .tx_counter_upd (tx_counter_upd), + .tx_data (tx_data), + .tx_data_valid (tx_data_valid), + .tx_done (tx_done), + .ctrl_rd_wr (ctrl_rd_wr), + .ctrl_addr (ctrl_addr), + .ctrl_addr_valid (ctrl_addr_valid), + .ctrl_data_rx (ctrl_data_rx), + .ctrl_data_rx_valid(ctrl_data_rx_valid), + .ctrl_data_tx (ctrl_data_tx), + .ctrl_data_tx_ready(ctrl_data_tx_ready), + .wrap_length (wrap_length) + ); + + spi_slave_dc_fifo #( + .DATA_WIDTH (32), + .BUFFER_DEPTH(8) + ) u_dcfifo_rx ( + .clk_a (spi_sclk), + .rstn_a (obi_aresetn), + .data_a (ctrl_data_rx), + .valid_a(ctrl_data_rx_valid), + .ready_a(ctrl_data_rx_ready), + .clk_b (obi_aclk), + .rstn_b (obi_aresetn), + .data_b (fifo_data_rx), + .valid_b(fifo_data_rx_valid), + .ready_b(fifo_data_rx_ready) + ); + + spi_slave_dc_fifo #( + .DATA_WIDTH (32), + .BUFFER_DEPTH(8) + ) u_dcfifo_tx ( + .clk_a (obi_aclk), + .rstn_a (obi_aresetn), + .data_a (fifo_data_tx), + .valid_a(fifo_data_tx_valid), + .ready_a(fifo_data_tx_ready), + .clk_b (spi_sclk), + .rstn_b (obi_aresetn), + .data_b (ctrl_data_tx), + .valid_b(ctrl_data_tx_valid), + .ready_b(ctrl_data_tx_ready) + ); + + spi_slave_obi_plug #( + .OBI_ADDR_WIDTH(OBI_ADDR_WIDTH), + .OBI_DATA_WIDTH(OBI_DATA_WIDTH) + ) u_obiplug ( + .obi_aclk (obi_aclk), + .obi_aresetn (obi_aresetn), + .obi_master_req (obi_master_req), + .obi_master_gnt (obi_master_gnt), + .obi_master_addr (obi_master_addr), + .obi_master_we (obi_master_we), + .obi_master_w_data (obi_master_w_data), + .obi_master_be (obi_master_be), + .obi_master_r_valid(obi_master_r_valid), + .obi_master_r_data (obi_master_r_data), + .rxtx_addr (addr_sync), + .rxtx_addr_valid (addr_valid_sync), + .start_tx (rd_wr_sync & addr_valid_sync), + .cs (cs_sync), + .tx_data (fifo_data_tx), + .tx_valid (fifo_data_tx_valid), + .tx_ready (fifo_data_tx_ready), + .rx_data (fifo_data_rx), + .rx_valid (fifo_data_rx_valid), + .rx_ready (fifo_data_rx_ready), + .wrap_length (wrap_length) + ); + + spi_slave_syncro #( + .AXI_ADDR_WIDTH(OBI_ADDR_WIDTH) + ) u_syncro ( + .sys_clk (obi_aclk), + .rstn (obi_aresetn), + .cs (spi_cs), + .address (ctrl_addr), + .address_valid (ctrl_addr_valid), + .rd_wr (ctrl_rd_wr), + .cs_sync (cs_sync), + .address_sync (addr_sync), + .address_valid_sync(addr_valid_sync), + .rd_wr_sync (rd_wr_sync) + ); + + assign test_mode = 1'b0; +endmodule diff --git a/hw/ip/obi_spi_slave/obi_spi_slave.vlt b/hw/ip/obi_spi_slave/obi_spi_slave.vlt new file mode 100644 index 000000000..5c3ca19eb --- /dev/null +++ b/hw/ip/obi_spi_slave/obi_spi_slave.vlt @@ -0,0 +1,13 @@ +// waiver file for obi_spi_slave + +`verilator_config + +lint_off -rule UNUSED -file "*/ip/obi_spi_slave/obi_spi_slave.sv" -match "Signal is not used: 'ctrl_data_rx_ready'*" +lint_off -rule UNUSED -file "*/ip/obi_spi_slave/obi_spi_slave.sv" -match "Signal is not used: 'ctrl_data_tx_valid'*" +lint_off -rule UNUSED -file "*/ip/obi_spi_slave/spi_slave_dc_fifo.sv" -match "Parameter is not used: 'BUFFER_DEPTH'*" +lint_off -rule UNUSED -file "*/ip/obi_spi_slave/spi_slave_controller.sv" -match "Parameter is not used: 'DUMMY_CYCLES'*" +lint_off -rule UNDRIVEN -file "*/core-v-mini-mcu/core_v_mini_mcu.sv" -match "Signal is not driven: 'spi_slave_resp'*" +lint_off -rule SYNCASYNCNET -file "*/system/x_heep_system.sv" -match "Signal flopped as both synchronous and async*" +lint_off -rule CASEINCOMPLETE -file "*/ip/obi_spi_slave/spi_slave_controller.sv" -match "Case values incompletely covered (example pattern 0x5)*" +lint_off -rule UNUSED -file "*/core-v-mini-mcu/ao_peripheral_subsystem.sv" -match "Signal is not used: 'spi_slave_miso_i'*" + diff --git a/hw/ip/obi_spi_slave/spi_slave_cmd_parser.sv b/hw/ip/obi_spi_slave/spi_slave_cmd_parser.sv new file mode 100644 index 000000000..f1e0585bb --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_cmd_parser.sv @@ -0,0 +1,135 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +`define write_mem 8'h2 +`define read_mem 8'hB +`define read_reg0 8'h7 +`define write_reg0 8'h11 +`define write_reg1 8'h20 +`define read_reg1 8'h21 +`define write_reg2 8'h30 +`define read_reg2 8'h31 + + + +module spi_slave_cmd_parser ( + input logic [7:0] cmd, + output logic get_addr, + output logic get_data, + output logic send_data, + output logic enable_cont, + output logic enable_regs, + output logic wait_dummy, + output logic [1:0] reg_sel +); + + + always_comb begin + get_addr = 0; + get_data = 0; + send_data = 0; + enable_cont = 0; + enable_regs = 1'b0; + wait_dummy = 0; + reg_sel = 2'b00; + case (cmd) + `write_mem: + begin + get_addr = 1; + get_data = 1; + send_data = 0; + enable_cont = 1'b1; + enable_regs = 1'b0; + wait_dummy = 0; + reg_sel = 2'b00; + end + `read_reg0: + begin + get_addr = 0; + get_data = 0; + send_data = 1; + enable_cont = 0; + enable_regs = 1'b1; + wait_dummy = 0; + reg_sel = 2'b00; + end + `read_mem: + begin + get_addr = 1; + get_data = 0; + send_data = 1; + enable_cont = 1'b1; + enable_regs = 1'b0; + wait_dummy = 1; + reg_sel = 2'b00; + end + `write_reg0: + begin + get_addr = 1'b0; + get_data = 1'b1; + send_data = 1'b0; + enable_cont = 1'b0; + enable_regs = 1'b1; + wait_dummy = 1'b0; + reg_sel = 2'b00; + end + `write_reg1: + begin + get_addr = 1'b0; + get_data = 1'b1; + send_data = 1'b0; + enable_cont = 1'b0; + enable_regs = 1'b1; + wait_dummy = 1'b0; + reg_sel = 2'b01; + end + `read_reg1: + begin + get_addr = 1'b0; + get_data = 1'b0; + send_data = 1'b1; + enable_cont = 1'b0; + enable_regs = 1'b1; + wait_dummy = 1'b0; + reg_sel = 2'b01; + end + `write_reg2: + begin + get_addr = 1'b0; + get_data = 1'b1; + send_data = 1'b0; + enable_cont = 1'b0; + enable_regs = 1'b1; + wait_dummy = 1'b0; + reg_sel = 2'b10; + end + `read_reg2: + begin + get_addr = 1'b0; + get_data = 1'b0; + send_data = 1'b1; + enable_cont = 1'b0; + enable_regs = 1'b1; + wait_dummy = 1'b0; + reg_sel = 2'b10; + end + default: begin + get_addr = 0; + get_data = 1'b0; + send_data = 0; + enable_cont = 0; + enable_regs = 1'b0; + wait_dummy = 0; + reg_sel = 2'b00; + end + endcase + end + +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_controller.sv b/hw/ip/obi_spi_slave/spi_slave_controller.sv new file mode 100644 index 000000000..f9c9e6a91 --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_controller.sv @@ -0,0 +1,253 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + + +module spi_slave_controller #( + parameter DUMMY_CYCLES = 32 +) ( + input logic sclk, + input logic sys_rstn, + input logic cs, + output logic [ 7:0] rx_counter, + output logic rx_counter_upd, + input logic [31:0] rx_data, + input logic rx_data_valid, + output logic [ 7:0] tx_counter, + output logic tx_counter_upd, + output logic [31:0] tx_data, + output logic tx_data_valid, + input logic tx_done, + output logic ctrl_rd_wr, + output logic [31:0] ctrl_addr, + output logic ctrl_addr_valid, + output logic [31:0] ctrl_data_rx, + output logic ctrl_data_rx_valid, + input logic [31:0] ctrl_data_tx, + output logic ctrl_data_tx_ready, + output logic [15:0] wrap_length +); + + localparam REG_SIZE = 8; + + + enum logic [2:0] { + CMD, + ADDR, + DATA_TX, + DATA_RX, + DUMMY + } + state, state_next; + + logic [ 7:0] command; + + logic decode_cmd_comb; + + logic [ 31:0] addr_reg; + logic [ 7:0] cmd_reg; + + logic sample_ADDR; + logic sample_CMD; + + logic get_addr; + logic wait_dummy; + logic get_data; + logic send_data; + logic enable_cont; + logic enable_regs; + logic [ 1:0] reg_sel; + logic [REG_SIZE-1:0] reg_data; + logic reg_valid; + + logic ctrl_data_tx_ready_next; + logic [ 7:0] tx_counter_next; + logic tx_counter_upd_next; + logic tx_data_valid_next; + logic tx_done_reg; + + logic [ 7:0] s_dummy_cycles; + + assign command = decode_cmd_comb ? rx_data[7:0] : cmd_reg; + + spi_slave_cmd_parser u_cmd_parser ( + .cmd (command), // In, + .get_addr (get_addr), // Out, + .get_data (get_data), // Out, + .send_data (send_data), // Out, + .wait_dummy (wait_dummy), // Out, + .enable_cont(enable_cont), // Out, + .enable_regs(enable_regs), // Out, + .reg_sel (reg_sel) // Out + ); + + spi_slave_regs #( + .REG_SIZE(REG_SIZE) + ) u_spiregs ( + .sclk(sclk), + .rstn(sys_rstn), + .wr_data(rx_data[REG_SIZE-1:0]), + .wr_addr(reg_sel), + .wr_data_valid(reg_valid), + .rd_data(reg_data), + .rd_addr(reg_sel), + .dummy_cycles(s_dummy_cycles), + .wrap_length(wrap_length) + ); + always_comb begin + rx_counter = 8'h1F; + rx_counter_upd = 0; + tx_counter_next = 8'h1F; + tx_counter_upd_next = 0; + decode_cmd_comb = 1'b0; + sample_ADDR = 1'b0; + sample_CMD = 1'b0; + ctrl_data_rx_valid = 1'b0; + ctrl_data_tx_ready_next = 1'b0; + reg_valid = 1'b0; + tx_data_valid_next = 1'b0; + state_next = state; + case (state) + CMD: begin + decode_cmd_comb = 1'b1; + ctrl_data_tx_ready_next = 1'b1; //empty TX fifo if not allready empty + if (rx_data_valid) begin + sample_CMD = 1'b1; + if (get_addr) begin + state_next = ADDR; + rx_counter_upd = 1; + rx_counter = 8'h1F; + end else if (get_data) begin + state_next = DATA_RX; + rx_counter_upd = 1; + if (enable_regs) rx_counter = 8'h7; + end else begin + state_next = DATA_TX; + tx_counter_upd_next = 1; + tx_data_valid_next = 1'b1; + tx_counter_next = 8'h1F; + if (~enable_regs) ctrl_data_tx_ready_next = 1'b1; + end + end else begin + state_next = CMD; + end + end + ADDR: begin + ctrl_data_tx_ready_next = 1'b1; + if (rx_data_valid) begin + sample_ADDR = 1'b1; + if (wait_dummy) begin + state_next = DUMMY; + rx_counter = s_dummy_cycles; + rx_counter_upd = 1; + end else if (send_data) begin + state_next = DATA_TX; + tx_counter_upd_next = 1; + tx_counter_next = 8'h1F; + end else if (get_data) begin + state_next = DATA_RX; + rx_counter_upd = 1; + rx_counter = 8'h1F; + end + end else begin + state_next = ADDR; + end + end + DUMMY: begin + if (rx_data_valid) begin + if (get_data) begin + state_next = DATA_RX; + rx_counter = 8'h1F; + rx_counter_upd = 1; + end else begin + state_next = DATA_TX; + tx_counter_next = 8'h1F; + tx_counter_upd_next = 1; + tx_data_valid_next = 1'b1; + if (~enable_regs) ctrl_data_tx_ready_next = 1'b1; + end + end else begin + state_next = DUMMY; + end + end + DATA_RX: begin + if (rx_data_valid) begin + if (enable_regs) reg_valid = 1'b1; + else ctrl_data_rx_valid = 1'b1; + if (enable_cont) begin + state_next = DATA_RX; + rx_counter = 8'h1F; + rx_counter_upd = 1; + end else begin + state_next = CMD; + rx_counter = 8'h7; + rx_counter_upd = 1; + end + end else begin + state_next = DATA_RX; + end + end + DATA_TX: begin + if (tx_done_reg) begin + if (enable_cont) begin + state_next = DATA_TX; + tx_counter_next = 8'h1F; + tx_counter_upd_next = 1; + tx_data_valid_next = 1'b1; + if (~enable_regs) ctrl_data_tx_ready_next = 1'b1; + end else begin + state_next = CMD; + rx_counter = 8'h7; + rx_counter_upd = 1; + end + end else begin + state_next = DATA_TX; + end + end + endcase + end + + + always @(posedge sclk or posedge cs) begin + if (cs == 1'b1) begin + state <= CMD; + end else begin + state <= state_next; + end + end + + always @(posedge sclk or posedge cs) begin + if (cs == 1'b1) begin + addr_reg <= 'h0; + cmd_reg <= 'h0; + tx_done_reg <= 1'b0; + ctrl_addr_valid <= 1'b0; + tx_counter_upd <= 1'b0; + tx_data_valid <= 1'b0; + ctrl_data_tx_ready <= 1'b0; + tx_counter <= 'h0; + tx_data <= 'h0; + end else begin + if (sample_ADDR) addr_reg <= rx_data; + if (sample_CMD) cmd_reg <= rx_data[7:0]; + ctrl_addr_valid <= sample_ADDR; + tx_counter_upd <= tx_counter_upd_next; + tx_counter <= tx_counter_next; + tx_data_valid <= tx_data_valid_next; + tx_done_reg <= tx_done; + ctrl_data_tx_ready <= ctrl_data_tx_ready_next; + tx_data <= (enable_regs) ? {24'b0, reg_data} : ctrl_data_tx; + end + end + + assign ctrl_data_rx = rx_data; + assign ctrl_addr = addr_reg; + assign ctrl_rd_wr = send_data; + +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_dc_fifo.sv b/hw/ip/obi_spi_slave/spi_slave_dc_fifo.sv new file mode 100644 index 000000000..86913b924 --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_dc_fifo.sv @@ -0,0 +1,50 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module spi_slave_dc_fifo #( + parameter DATA_WIDTH = 32, + parameter BUFFER_DEPTH = 8 +) ( + input logic clk_a, + input logic rstn_a, + input logic [DATA_WIDTH-1:0] data_a, + input logic valid_a, + output logic ready_a, + input logic clk_b, + input logic rstn_b, + output logic [DATA_WIDTH-1:0] data_b, + output logic valid_b, + input logic ready_b +); + + typedef logic [DATA_WIDTH-1:0] data_t; + + cdc_fifo_gray #( + .WIDTH(32), + .T(data_t), + .LOG_DEPTH(4), + .SYNC_STAGES(2) + ) i_cdc_fifo_gray ( + + .src_rst_ni (rstn_a), + .src_clk_i (clk_a), + .src_data_i (data_a), + .src_valid_i(valid_a), + .src_ready_o(ready_a), + + .dst_rst_ni (rstn_b), + .dst_clk_i (clk_b), + .dst_data_o (data_b), + .dst_valid_o(valid_b), + .dst_ready_i(ready_b) + + ); + +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_obi_plug.sv b/hw/ip/obi_spi_slave/spi_slave_obi_plug.sv new file mode 100644 index 000000000..c28955605 --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_obi_plug.sv @@ -0,0 +1,188 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +`define WRITING 1'b1 +`define READING 1'b0 + +module spi_slave_obi_plug #( + parameter OBI_ADDR_WIDTH = 32, + parameter OBI_DATA_WIDTH = 32 +) ( + // OBI MASTER + //*************************************** + input logic obi_aclk, + input logic obi_aresetn, + + // ADDRESS CHANNEL + output logic obi_master_req, + input logic obi_master_gnt, + output logic [OBI_ADDR_WIDTH-1:0] obi_master_addr, + output logic obi_master_we, + output logic [OBI_DATA_WIDTH-1:0] obi_master_w_data, + output logic [ 3:0] obi_master_be, + + // RESPONSE CHANNEL + input logic obi_master_r_valid, + input logic [OBI_DATA_WIDTH-1:0] obi_master_r_data, + + //SPI/BUFFER + input logic [OBI_ADDR_WIDTH-1:0] rxtx_addr, + input logic rxtx_addr_valid, + input logic start_tx, + input logic cs, + output logic [31:0] tx_data, + output logic tx_valid, + input logic tx_ready, + input logic [31:0] rx_data, + input logic rx_valid, + output logic rx_ready, + + input logic [15:0] wrap_length +); + + logic [OBI_ADDR_WIDTH-1:0] curr_addr; + logic [OBI_ADDR_WIDTH-1:0] next_addr; + logic [ 31:0] curr_data_rx; + logic [OBI_DATA_WIDTH-1:0] curr_data_tx; + logic sample_fifo; + logic sample_obidata; + logic sample_rxtx_state; + logic rxtx_state; + logic [ 0:0] curr_rxtx_state; //low for reading, high for writing + logic incr_addr_w; + logic incr_addr_r; + + + + // up to 64 kwords (256kB) + logic [ 15:0] tx_counter; + + logic [ 15:0] wrap_length_t; + + + enum logic [1:0] { + IDLE, + OBIADDR, + OBIRESP, + DATA + } + OBI_CS, OBI_NS; + + // Check if the wrap lenght is equal to '0' + assign wrap_length_t = (wrap_length == 0) ? 16'h1 : wrap_length; + + + always_ff @(posedge obi_aclk or negedge obi_aresetn) begin + if (obi_aresetn == 0) begin + OBI_CS <= IDLE; + curr_data_rx <= 'h0; + curr_data_tx <= 'h0; + curr_addr <= 'h0; + curr_rxtx_state <= 'h0; + end else begin + OBI_CS <= OBI_NS; + if (sample_fifo) curr_data_rx <= rx_data; + if (sample_obidata) curr_data_tx <= obi_master_r_data; + if (sample_rxtx_state) curr_rxtx_state <= rxtx_state; + if (rxtx_addr_valid) curr_addr <= rxtx_addr; + else if (incr_addr_w | incr_addr_r) curr_addr <= next_addr; + end + end + + always_ff @(posedge obi_aclk or negedge obi_aresetn) begin + if (obi_aresetn == 1'b0) tx_counter <= 16'h0; + else if (start_tx) tx_counter <= 16'h0; + else if (incr_addr_w | incr_addr_r) begin + if (tx_counter == wrap_length_t - 1) tx_counter <= 16'h0; + else tx_counter <= tx_counter + 16'h1; + end + end + + always_comb begin + next_addr = 32'b0; + if (rxtx_addr_valid) next_addr = rxtx_addr; + else if (tx_counter == wrap_length_t - 1) next_addr = rxtx_addr; + else next_addr = curr_addr + 32'h4; + end + + + always_comb begin + OBI_NS = IDLE; + sample_fifo = 1'b0; + rx_ready = 1'b0; + tx_valid = 1'b0; + obi_master_req = 1'b0; + obi_master_we = 1'b0; + sample_obidata = 1'b0; + sample_rxtx_state = 1'b0; + rxtx_state = 1'b0; + incr_addr_w = 1'b0; + incr_addr_r = 1'b0; + case (OBI_CS) + IDLE: begin + if (rx_valid) begin + sample_fifo = 1'b1; + rx_ready = 1'b1; + rxtx_state = `WRITING; + sample_rxtx_state = 1'b1; + OBI_NS = OBIADDR; + end else if (start_tx && !cs) begin + rxtx_state = `READING; + sample_rxtx_state = 1'b1; + OBI_NS = OBIADDR; + end else begin + OBI_NS = IDLE; + end + end + OBIADDR: begin + if (curr_rxtx_state == `WRITING) begin + obi_master_we = 1'b1; + end + + obi_master_req = 1'b1; + + if (obi_master_gnt && ((tx_ready && (curr_rxtx_state == `READING)) || (curr_rxtx_state == `WRITING))) OBI_NS = OBIRESP; + else OBI_NS = OBIADDR; + end + OBIRESP: begin + if (obi_master_r_valid) begin + OBI_NS = IDLE; + if (curr_rxtx_state == `READING) begin + sample_obidata = 1'b1; + OBI_NS = DATA; + end else incr_addr_w = 1'b1; + end else OBI_NS = OBIRESP; + end + DATA: begin + tx_valid = 1'b1; + if (cs) begin + OBI_NS = IDLE; + end else begin + if (tx_ready) begin + if (tx_counter == wrap_length_t - 1) begin + OBI_NS = IDLE; + end else begin + OBI_NS = OBIADDR; + end + incr_addr_r = 1'b1; + end else begin + OBI_NS = DATA; + end + end + + end + endcase + end + + assign tx_data = curr_data_tx; + assign obi_master_addr = curr_addr; + assign obi_master_w_data = curr_data_rx; + assign obi_master_be = 4'b1111; +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_regs.sv b/hw/ip/obi_spi_slave/spi_slave_regs.sv new file mode 100644 index 000000000..45778ff1f --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_regs.sv @@ -0,0 +1,59 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module spi_slave_regs #( + parameter REG_SIZE = 8 +) ( + input logic sclk, + input logic rstn, + input logic [REG_SIZE-1:0] wr_data, + input logic [ 1:0] wr_addr, + input logic wr_data_valid, + output logic [REG_SIZE-1:0] rd_data, + input logic [ 1:0] rd_addr, + output logic [ 7:0] dummy_cycles, + output logic [ 15:0] wrap_length +); + + logic [REG_SIZE-1:0] reg0; // number of dummy cycles + logic [REG_SIZE-1:0] reg1; // wrap length, low + logic [REG_SIZE-1:0] reg2; // wrap length, high + + assign dummy_cycles = reg0; + assign wrap_length = {reg2, reg1}; + + always_comb begin + case (rd_addr) + 2'b00: rd_data = reg0; + 2'b01: rd_data = reg1; + 2'b10: rd_data = reg2; + default: begin + end + endcase + end + + always @(posedge sclk or negedge rstn) begin + if (rstn == 0) begin + reg0 <= 'd32; + reg1 <= 'h0; + reg2 <= 'h0; + end else begin + if (wr_data_valid) begin + case (wr_addr) + 2'b00: reg0 <= wr_data; + 2'b01: reg1 <= wr_data; + 2'b10: reg2 <= wr_data; + default:begin + end + endcase + end + end + end +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_rx.sv b/hw/ip/obi_spi_slave/spi_slave_rx.sv new file mode 100644 index 000000000..d60785c8a --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_rx.sv @@ -0,0 +1,72 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module spi_slave_rx ( + input logic sclk, + input logic cs, + input logic mosi, + input logic [ 7:0] counter_in, + input logic counter_in_upd, + output logic [31:0] data, + output logic data_ready +); + + reg [31:0] data_int; + reg [31:0] data_int_next; + reg [ 7:0] counter; + reg [ 7:0] counter_trgt; + reg [ 7:0] counter_next; + reg [ 7:0] counter_trgt_next; + + logic running; + logic running_next; + + assign data = data_int_next; + + always_comb begin + if (counter_in_upd) counter_trgt_next = counter_in; + else if (counter_trgt == 8'h1) counter_trgt_next = 8'h7; + else counter_trgt_next = counter_trgt; + + if (counter_in_upd) running_next = 1'b1; + else if (counter == counter_trgt) running_next = 1'b0; + else running_next = running; + + if (running) begin + if (counter == counter_trgt) begin + counter_next = 'h0; + data_ready = 1'b1; + end else begin + counter_next = counter + 1; + data_ready = 1'b0; + end + data_int_next = {data_int[30:0], mosi}; + end else begin + counter_next = counter; + data_ready = 1'b0; + data_int_next = data_int; + end + end + + + always @(posedge sclk or posedge cs) begin + if (cs == 1'b1) begin + counter <= 0; + counter_trgt <= 'h1; + data_int <= 'h0; + running <= 'h1; + end else begin + counter <= counter_next; + counter_trgt <= counter_trgt_next; + data_int <= data_int_next; + running <= running_next; + end + end +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_syncro.sv b/hw/ip/obi_spi_slave/spi_slave_syncro.sv new file mode 100644 index 000000000..0fb78ea9c --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_syncro.sv @@ -0,0 +1,47 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module spi_slave_syncro #( + parameter AXI_ADDR_WIDTH = 32 +) ( + input logic sys_clk, + input logic rstn, + input logic cs, + input logic [AXI_ADDR_WIDTH-1:0] address, + input logic address_valid, + input logic rd_wr, + output logic cs_sync, + output logic [AXI_ADDR_WIDTH-1:0] address_sync, + output logic address_valid_sync, + output logic rd_wr_sync +); + + logic [1:0] cs_reg; + logic [2:0] valid_reg; + logic [1:0] rdwr_reg; + + assign cs_sync = cs_reg[1]; + assign address_valid_sync = ~valid_reg[2] & valid_reg[1]; //detect rising edge of addr valid + assign address_sync = address; + assign rd_wr_sync = rdwr_reg[1]; + + always @(posedge sys_clk or negedge rstn) begin + if (rstn == 1'b0) begin + cs_reg <= 2'b11; + valid_reg <= 3'b000; + rdwr_reg <= 2'b00; + end else begin + cs_reg <= {cs_reg[0], cs}; + valid_reg <= {valid_reg[1:0], address_valid}; + rdwr_reg <= {rdwr_reg[0], rd_wr}; + end + end + +endmodule diff --git a/hw/ip/obi_spi_slave/spi_slave_tx.sv b/hw/ip/obi_spi_slave/spi_slave_tx.sv new file mode 100644 index 000000000..a0d05f0ef --- /dev/null +++ b/hw/ip/obi_spi_slave/spi_slave_tx.sv @@ -0,0 +1,88 @@ +// Copyright 2017 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the “License”); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +module spi_slave_tx ( + input logic test_mode, + input logic sclk, + input logic cs, + output logic miso, + input logic [ 7:0] counter_in, + input logic counter_in_upd, + input logic [31:0] data, + input logic data_valid, + output logic done +); + + reg [31:0] data_int; + reg [31:0] data_int_next; + reg [7:0] counter; + reg [7:0] counter_trgt; + reg [7:0] counter_next; + reg [7:0] counter_trgt_next; + logic running; + logic running_next; + logic sclk_inv; + logic sclk_test; + + assign miso = data_int[31]; + + + always_comb begin + done = 1'b0; + if (counter_in_upd) counter_trgt_next = counter_in; + else counter_trgt_next = counter_trgt; + + if (counter_in_upd) running_next = 1'b1; + else if (counter == counter_trgt) running_next = 1'b0; + else running_next = running; + + if (running || counter_in_upd) begin + if (counter == counter_trgt) begin + done = 1'b1; + counter_next = 0; + end else counter_next = counter + 1; + + if (data_valid) begin + data_int_next = data; + end else begin + data_int_next = {data_int[30:0], 1'b0}; + end + end else begin + counter_next = counter; + data_int_next = data_int; + end + end + + pulp_clock_inverter clk_inv_i ( + .clk_i(sclk), + .clk_o(sclk_inv) + ); + + pulp_clock_mux2 clk_mux_i ( + .clk0_i(sclk_inv), + .clk1_i(sclk), + .clk_sel_i(test_mode), + .clk_o(sclk_test) + ); + + always @(posedge sclk_test or posedge cs) begin + if (cs == 1'b1) begin + counter <= 'h0; + counter_trgt <= 'h7; + data_int <= 'h0; + running <= 1'b0; + end else begin + counter <= counter_next; + counter_trgt <= counter_trgt_next; + data_int <= data_int_next; + running <= running_next; + end + end +endmodule diff --git a/hw/ip/obi_spi_slave/src_files.yml b/hw/ip/obi_spi_slave/src_files.yml new file mode 100644 index 000000000..35bd8c0b2 --- /dev/null +++ b/hw/ip/obi_spi_slave/src_files.yml @@ -0,0 +1,12 @@ +axi_spi_slave: + files: [ + axi_spi_slave.sv, + spi_slave_axi_plug.sv, + spi_slave_cmd_parser.sv, + spi_slave_controller.sv, + spi_slave_dc_fifo.sv, + spi_slave_regs.sv, + spi_slave_rx.sv, + spi_slave_syncro.sv, + spi_slave_tx.sv, + ] diff --git a/hw/ip/pdm2pcm/rtl/pdm2pcm.sv b/hw/ip/pdm2pcm/rtl/pdm2pcm.sv index dd3a9e58a..43a6de7a9 100644 --- a/hw/ip/pdm2pcm/rtl/pdm2pcm.sv +++ b/hw/ip/pdm2pcm/rtl/pdm2pcm.sv @@ -27,32 +27,32 @@ module pdm2pcm #( import pdm2pcm_reg_pkg::*; - logic [ 15:0] par_clkdiv_idx; - logic [ 3:0] par_decim_idx_combs; - logic [ 4:0] par_decim_idx_hfbd2; - logic [ 5:0] par_decim_idx_fir; - logic [ 17:0] coeffs_hb1 [ 0:3]; - logic [ 17:0] coeffs_hb2 [ 0:6]; - logic [ 17:0] coeffs_fir [0:13]; + logic [ 15:0] par_clkdiv_idx; + logic [ 3:0] par_decim_idx_combs; + logic [ 4:0] par_decim_idx_hfbd2; + logic [ 5:0] par_decim_idx_fir; + logic [ 17:0] coeffs_hb1 [ 0:3]; + logic [ 17:0] coeffs_hb2 [ 0:6]; + logic [ 17:0] coeffs_fir [0:13]; - logic [FIFO_ADDR_WIDTH-1:0] fifo_usage; + logic [FIFO_ADDR_WIDTH-1:0] fifo_usage; - logic pcm_data_valid; + logic pcm_data_valid; - logic rx_ready; + logic rx_ready; - logic [ 17:0] pcm; + logic [ 17:0] pcm; // FIFO/window related signals - logic [ 31:0] rx_data; - logic [ FIFO_WIDTH-1:0] rx_fifo; + logic [ 31:0] rx_data; + logic [ FIFO_WIDTH-1:0] rx_fifo; // Interface signals - pdm2pcm_reg2hw_t reg2hw; - pdm2pcm_hw2reg_t hw2reg; + pdm2pcm_reg2hw_t reg2hw; + pdm2pcm_hw2reg_t hw2reg; - reg_req_t [ 0:0] fifo_win_h2d; - reg_rsp_t [ 0:0] fifo_win_d2h; + reg_req_t [ 0:0] fifo_win_h2d; + reg_rsp_t [ 0:0] fifo_win_d2h; logic push, pop; logic empty, full; diff --git a/mcu_cfg.hjson b/mcu_cfg.hjson index 316750e18..dcb7d2144 100644 --- a/mcu_cfg.hjson +++ b/mcu_cfg.hjson @@ -120,7 +120,7 @@ pdm2pcm: { offset: 0x00060000, length: 0x00010000, - is_included: "no", + is_included: "yes", path: "./hw/ip/pdm2pcm/data/pdm2pcm.hjson" }, i2s: { diff --git a/pad_cfg.hjson b/pad_cfg.hjson index b0720c2ec..247b9d748 100644 --- a/pad_cfg.hjson +++ b/pad_cfg.hjson @@ -85,7 +85,7 @@ type: output }, gpio: { - num: 18, + num: 14, num_offset: 0, #first gpio is gpio0 type: inout }, @@ -113,7 +113,54 @@ num: 4, type: inout }, - + spi_slave_sck: { + num: 1, + type: inout + mux: { + spi_slave_sck: { + type: inout + }, + gpio_14: { + type: inout + } + } + }, + spi_slave_cs: { + num: 1, + type: inout + mux: { + spi_slave_cs: { + type: inout + }, + gpio_15: { + type: inout + } + } + }, + spi_slave_miso: { + num: 1, + type: inout + mux: { + spi_slave_miso: { + type: inout + }, + gpio_16: { + type: inout + } + } + }, + spi_slave_mosi: { + num: 1, + type: inout + mux: { + spi_slave_mosi: { + type: inout + }, + gpio_17: { + type: inout + } + } + }, pdm2pcm_pdm: { num: 1, type: inout diff --git a/sw/applications/example_spi_obi_slave/buffer.h b/sw/applications/example_spi_obi_slave/buffer.h new file mode 100644 index 000000000..159c5b08c --- /dev/null +++ b/sw/applications/example_spi_obi_slave/buffer.h @@ -0,0 +1,59 @@ +#define DATA_1_LENGTH 33*8*4 //In bytes +#define DATA_2_LENGTH 20*4 //In bytes +#define DATA_3_LENGTH 67*4 //In bytes + +uint32_t test_data_1[DATA_1_LENGTH/4] = { + 0x76543211, 0xfedcba99, 0x579a6f91, 0x657d5bef, 0x758ee420, 0x01234568, 0xfedbca97, 0x89abde00, + 0x76543212, 0xfedcba9a, 0x579a6f92, 0x657d5bf0, 0x758ee421, 0x01234569, 0xfedbca98, 0x89abde01, + 0x76543213, 0xfedcba9b, 0x579a6f93, 0x657d5bf1, 0x758ee422, 0x0123456a, 0xfedbca99, 0x89abde02, + 0x76543214, 0xfedcba9c, 0x579a6f94, 0x657d5bf2, 0x758ee423, 0x0123456b, 0xfedbca9a, 0x89abde03, + 0x76543215, 0xfedcba9d, 0x579a6f95, 0x657d5bf3, 0x758ee424, 0x0123456c, 0xfedbca9b, 0x89abde04, + 0x76543216, 0xfedcba9e, 0x579a6f96, 0x657d5bf4, 0x758ee425, 0x0123456d, 0xfedbca9c, 0x89abde05, + 0x76543217, 0xfedcba9f, 0x579a6f97, 0x657d5bf5, 0x758ee426, 0x0123456e, 0xfedbca9d, 0x89abde06, + 0x76543218, 0xfedcbaa0, 0x579a6f98, 0x657d5bf6, 0x758ee427, 0x0123456f, 0xfedbca9e, 0x89abde07, + 0x76543219, 0xfedcbaa1, 0x579a6f99, 0x657d5bf7, 0x758ee428, 0x01234570, 0xfedbca9f, 0x89abde08, + 0x7654321a, 0xfedcbaa2, 0x579a6f9a, 0x657d5bf8, 0x758ee429, 0x01234571, 0xfedbcaa0, 0x89abde09, + 0x7654321b, 0xfedcbaa3, 0x579a6f9b, 0x657d5bf9, 0x758ee42a, 0x01234572, 0xfedbcaa1, 0x89abde0a, + 0x7654321c, 0xfedcbaa4, 0x579a6f9c, 0x657d5bfa, 0x758ee42b, 0x01234573, 0xfedbcaa2, 0x89abde0b, + 0x7654321d, 0xfedcbaa5, 0x579a6f9d, 0x657d5bfb, 0x758ee42c, 0x01234574, 0xfedbcaa3, 0x89abde0c, + 0x7654321e, 0xfedcbaa6, 0x579a6f9e, 0x657d5bfc, 0x758ee42d, 0x01234575, 0xfedbcaa4, 0x89abde0d, + 0x7654321f, 0xfedcbaa7, 0x579a6f9f, 0x657d5bfd, 0x758ee42e, 0x01234576, 0xfedbcaa5, 0x89abde0e, + 0x76543220, 0xfedcbaa8, 0x579a6fa0, 0x657d5bfe, 0x758ee42f, 0x01234577, 0xfedbcaa6, 0x89abde0f, + 0x76543221, 0xfedcbaa9, 0x579a6fa1, 0x657d5bff, 0x758ee430, 0x01234578, 0xfadbcaa7, 0x89abde10, + 0x76543222, 0xfedcbaaa, 0x579a6fa2, 0x657d5c00, 0x758ee431, 0x01234579, 0xfadbcaa8, 0x89abde11, + 0x76543223, 0xfedcbaab, 0x579a6fa3, 0x657d5c01, 0x758ee432, 0x0123457a, 0xfadbcaa9, 0x89abde12, + 0x76543224, 0xfedcbaac, 0x579a6fa4, 0x657d5c02, 0x758ee433, 0x0123457b, 0xfadbcaaa, 0x89abde13, + 0x76543225, 0xfedcbaad, 0x579a6fa5, 0x657d5c03, 0x758ee434, 0x0123457c, 0xfadbcaab, 0x89abde14, + 0x76543226, 0xfedcbaae, 0x579a6fa6, 0x657d5c04, 0x758ee435, 0x0123457d, 0xfadbcaac, 0x89abde15, + 0x76543227, 0xfedcbaaf, 0x579a6fa7, 0x657d5c05, 0x758ee436, 0x0123457e, 0xfadbcaad, 0x89abde16, + 0x76543228, 0xfedcbab0, 0x579a6fa8, 0x657d5c06, 0x758ee437, 0x0123457f, 0xfadbcaae, 0x89abde17, + 0x76543220, 0xfedcbaa8, 0x579a6fa0, 0x657d5bfe, 0x758ee42f, 0x01234577, 0xfedbcaa6, 0x89abde0f, + 0x76543221, 0xfedcbaa9, 0x579a6fa1, 0x657d5bff, 0x758ee430, 0x01234578, 0xfadbcaa7, 0x89abde10, + 0x76543222, 0xfedcbaaa, 0x579a6fa2, 0x657d5c00, 0x758ee431, 0x01234579, 0xfadbcaa8, 0x89abde11, + 0x76543223, 0xfedcbaab, 0x579a6fa3, 0x657d5c01, 0x758ee432, 0x0123457a, 0xfadbcaa9, 0x89abde12, + 0x76543224, 0xfedcbaac, 0x579a6fa4, 0x657d5c02, 0x758ee433, 0x0123457b, 0xfadbcaaa, 0x89abde13, + 0x76543225, 0xfedcbaad, 0x579a6fa5, 0x657d5c03, 0x758ee434, 0x0123457c, 0xfadbcaab, 0x89abde14, + 0x76543226, 0xfedcbaae, 0x579a6fa6, 0x657d5c04, 0x758ee435, 0x0123457d, 0xfadbcaac, 0x89abde15, + 0x76543227, 0xfedcbaaf, 0x579a6fa7, 0x657d5c05, 0x758ee436, 0x0123457e, 0xfadbcaad, 0x89abde16, + 0x76543228, 0xfedcbab0, 0x579a6fa8, 0x657d5c06, 0x758ee437, 0x0123457f, 0xfadbcaae, 0x89abde17 +}; + + +uint32_t test_data_2[DATA_2_LENGTH/4] = { + 0x12345678, 0xabcdef01, 0x87654321, 0x0fedcba9, 0x11223344, 0x99aabbcc, 0x55ffccaa, 0x10203040, + 0xaabbccdd, 0x33445566, 0x77889900, 0xffeeddcc, 0xdeadbeef, 0xcafebabe, 0x0badc0de, 0xbeadfeed, + 0x01234567, 0x89abcdef, 0x76543210, 0xf0e1d2c3 +}; + + +uint32_t test_data_3[DATA_3_LENGTH/4] = { + 0x1a2b3c4d, 0x5e6f7283, 0x9abcdef0, 0x01234567, 0x89abcdef, 0x76543210, 0xabcdef01, 0xdeadbeef, + 0xcafebabe, 0x0badc0de, 0xfacefeed, 0x11223344, 0x55667788, 0x99aabbcc, 0xddeeff00, 0x01234567, + 0x89abcdef, 0x1f2e3d4c, 0x5a6b7c8d, 0x9e0f1a2b, 0x3c4d5e6f, 0x7e829abc, 0xdef01234, 0x56789abc, + 0xabcdef01, 0xfedcba98, 0x76543210, 0x01234567, 0x89abcdef, 0x22334455, 0x66778899, 0xaabbccdd, + 0xdeadbeef, 0xcafebabe, 0x0badc0de, 0xfacefeed, 0xbadc0ffe, 0xd00dfeed, 0xfeedc0de, 0xbeadfeed, + 0xf00dbabe, 0xc001d00d, 0xabcddcba, 0x12345678, 0x9abcdef0, 0xabcdef01, 0x87654321, 0xfedcba98, + 0x01234567, 0x89abcdef, 0xfacecafe, 0xdeadf00b, 0xcafef00d, 0x00112233, 0x44556677, 0x8899aabb, + 0xccddeeff, 0x10203040, 0x50607080, 0x90a0b0c0, 0xd0e0f000, 0x12345678, 0xdeadbeef, 0xcafebabe, + 0x89abcdef, 0x22334455, 0x66778899 +}; diff --git a/sw/applications/example_spi_obi_slave/main.c b/sw/applications/example_spi_obi_slave/main.c new file mode 100644 index 000000000..b0917d54c --- /dev/null +++ b/sw/applications/example_spi_obi_slave/main.c @@ -0,0 +1,441 @@ +/** + * @file main.c + * @brief SPI OBI slave IP functionality test + * @author Fabian Aegerter + * +*/ + + +#include +#include + +#include "x-heep.h" +#include "spi_host.h" +#include "buffer.h" + + +/** Macros for operations. */ +#define DIV_ROUND_UP(numerator, denominator) ((numerator + denominator - 1) / denominator) +#define LOWER_BYTE_16_BITS(bytes)(bytes & 0xFF) +#define UPPER_BYTE_16_BITS(bytes)((bytes & 0xFF00) >> 8) +#define LOWER_BYTES_32_BITS(bytes)(bytes & 0xFFFF) +#define UPPER_BYTES_32_BITS(bytes)((bytes & 0xFFFF0000) >> 16) + +/** Macros for SPI SLAVE hardware. */ +#define WRITE_SPI_SLAVE_REG_0 0x11 +#define WRITE_SPI_SLAVE_REG_1 0x20 +#define WRITE_SPI_SLAVE_REG_2 0x30 +#define READ_SPI_SLAVE_CMD 0xB +#define WRITE_SPI_SLAVE_CMD 0x2 +#define WORD_SIZE_IN_BYTES 4 +#define MAX_DATA_SIZE 0x10000 //The values for Max data size and last valid address are arbitrary and aren't correct. + //They exist to highlight their importance for an actual implementation. + +#define LAST_VALID_ADDRESS 0x40000 //This code assumes that the memory addresses start at 0. + //Hence the first valid address hasn't been included + +/** Enum for SPI operation status flags. */ +typedef enum { + // Everithing went well + SPI_FLAG_SUCCESS = 0x0000, + //The SPI host was not properly initalized + SPI_HOST_FLAG_NOT_INIT = 0x0001, + //The target address is invalid + SPI_SLAVE_FLAG_ADDRESS_INVALID = 0x0002, + // The CSID was out of the bounds specified in SPI_HOST_PARAM_NUM_C_S + SPI_HOST_FLAG_CSID_INVALID = 0x0003, + //The amount of data exceeds the memory capacity of the SPI SLAVE (X-HEEP) + SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED = 0x0004, +} spi_flags_e; + + +spi_host_t* spi_hst; + +/** Lists used for testing. */ +uint32_t target_data_1[DATA_1_LENGTH/4]; //The target destination of the dataset 1 in the RAM +uint32_t target_data_2[DATA_2_LENGTH/4]; //The target destination of the dataset 2 in the RAM +uint32_t target_data_3[DATA_3_LENGTH/4]; //The target destination of the dataset 3 in the RAM + + + +/** Enums used for testing. */ +typedef enum { + OPERATION_WRITE, + OPERATION_READ +} OperationType; + +typedef enum{ + DATASET_1, + DATASET_2, + DATASET_3 +} DatasetNumber; + +/** Structs used for testing. */ +typedef struct { + uint8_t dataset_number; + OperationType operation_type; + uint8_t dummy_cycles; +} TestOperation; + +typedef struct { + uint32_t *test_data; + uint32_t *target_data; + uint16_t data_length; +} Dataset; + +spi_flags_e spi_host_init(spi_host_t* host); +static spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length); +spi_flags_e spi_slave_read(uint32_t addr, void* data, uint16_t length, uint8_t dummy_cycles); +bool check_address_validity(uint32_t addr, void* data, uint16_t length); +void print_array(const char *label, uint32_t *array, uint16_t size); +static void configure_spi(); +void spi_slave_write_dummy_cycles(uint8_t cycles); +void spi_host_wait(uint8_t cycles); +uint32_t make_word_compatible_for_spi_host(uint32_t word); +void make_compare_data_compatible(uint32_t *compare_data, uint16_t length); +void spi_slave_write_wrap_length(uint16_t length); +static void send_command_to_spi_host(uint32_t len, bool csaat, uint8_t direction); +bool test_process(Dataset *dataset, OperationType operation, uint8_t dummy_cycles); + +int main(int argc, char *argv[]) +{ + spi_hst = spi_host1; + spi_return_flags_e flags; + flags = spi_host_init(spi_hst); + if (flags != SPI_FLAG_SUCCESS){ + printf("Failure to initialize\n Error code: %d", flags); + return EXIT_FAILURE; + } + + Dataset all_test_datasets[] = { {test_data_1, target_data_1, DATA_1_LENGTH}, + {test_data_2, target_data_2, DATA_2_LENGTH}, + {test_data_3, target_data_3, DATA_3_LENGTH} + }; + + //Every entry of this array is a test that gets executed with the desired dataset, operation type and amount of dummy cycles + TestOperation test_order[] = {{DATASET_1, OPERATION_WRITE, 0}, //dummy_cycles = 0 (not used) + {DATASET_1, OPERATION_READ, 32}, + {DATASET_2, OPERATION_WRITE, 0}, //dummy_cycles = 0 (not used) + {DATASET_2, OPERATION_READ, 16}, + {DATASET_3, OPERATION_WRITE, 0}, //dummy_cycles = 0 (not used) + {DATASET_3, OPERATION_READ, 8}, + {DATASET_1, OPERATION_READ, 6}, + {DATASET_2, OPERATION_READ, 6}, //The minimum amount of dummy cycles is 6. + {DATASET_3, OPERATION_READ, 6} //Otherwise it breaks. + }; + + + for(uint32_t i = 0; i < sizeof(test_order) / sizeof(test_order[0]); i++){ + if(test_process(&all_test_datasets[test_order[i].dataset_number], test_order[i].operation_type, test_order[i].dummy_cycles) == EXIT_FAILURE){ + printf("Error in operation: %d", i); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} + +static void send_command_to_spi_host(uint32_t len, bool csaat, uint8_t direction){ + if(direction != SPI_DIR_DUMMY){ + len--; //The SPI HOST IP uses len-1 = amount of bytes to read and write. But also len = amount of dummy cycles + } + const uint32_t send_cmd_byte = spi_create_command((spi_command_t){ + .len = len, + .csaat = csaat, // Command not finished e.g. CS remains low after transaction + .speed = SPI_SPEED_STANDARD, // Single speed + .direction = direction + }); + spi_set_command(spi_hst, send_cmd_byte); + spi_wait_for_ready(spi_hst); +} + +spi_flags_e spi_slave_read(uint32_t addr, void* data, uint16_t length, uint8_t dummy_cycles){ + /* The OBI SPI slave IP has no way of knowing or handling incorrect addresses. + * Using an incorrect address leads to an abort of the simulation and it is + * unkown what happens when this issue occurs after physical implementation. + * This is why it is absolutely necessary to include checks such as the following two lines. + */ + if(DIV_ROUND_UP(length, WORD_SIZE_IN_BYTES) > MAX_DATA_SIZE) return SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED; + if (addr % 4 != 0 || (addr + length) > LAST_VALID_ADDRESS) return SPI_SLAVE_FLAG_ADDRESS_INVALID; + + + spi_slave_write_dummy_cycles(dummy_cycles); + + spi_slave_write_wrap_length(length); + + + spi_write_byte(spi_hst, READ_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + //write address + spi_write_word(spi_hst, make_word_compatible_for_spi_host(addr)); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); + + spi_host_wait(dummy_cycles); + + send_command_to_spi_host(length, false, SPI_DIR_RX_ONLY); + + /* + * Set RX watermark to length. The watermark is in words. + * If the length is not a multiple of 4, the RX watermark is set to length/4+1 + * to take into account the extra bytes. + * If the length is higher then the RX FIFO depth, the RX watermark is set to + * RX FIFO depth. In this case the flag is not set to 0, so the loop will + * continue until all the data is read. + */ + bool flag = 1; + uint16_t to_read = 0; + uint16_t i_start = 0; + uint16_t length_original = length; + uint32_t *data_32bit = (uint32_t *)data; + while (flag) { + if (length >= SPI_HOST_PARAM_RX_DEPTH) { + spi_set_rx_watermark(spi_hst, SPI_HOST_PARAM_RX_DEPTH>>2); + length -= SPI_HOST_PARAM_RX_DEPTH; + to_read += SPI_HOST_PARAM_RX_DEPTH; + } + else { + spi_set_rx_watermark(spi_hst, (length%4==0 ? length>>2 : (length>>2)+1)); + to_read += length; + flag = 0; + } + // Wait till SPI Host RX FIFO is full (or I read all the data) + spi_wait_for_rx_watermark(spi_hst); + // Read data from SPI Host RX FIFO + for (uint16_t i = i_start; i < to_read>>2; i++) { + spi_read_word(spi_hst, &data_32bit[i]); // Writes a full word + } + // Update the starting index + i_start += SPI_HOST_PARAM_RX_DEPTH>>2; + } + // Take into account the extra bytes (if any) + if (length_original % 4 != 0) { + uint32_t last_word = 0; + spi_read_word(spi_hst, &last_word); + memcpy(&data_32bit[length_original>>2], &last_word, length%4); + } + spi_wait_for_rx_empty(spi_hst); + return SPI_FLAG_SUCCESS; // Success +} + + +spi_flags_e spi_host_init(spi_host_t* host) { + + // Set the global spi variable to the one passed as argument. + spi_hst = host; + + // Enable spi host device + spi_return_flags_e test = spi_set_enable(spi_hst, true); + if(test != SPI_FLAG_SUCCESS){ + return SPI_HOST_FLAG_NOT_INIT; + }; + + // Enable spi output + if(spi_output_enable(spi_hst, true) != SPI_FLAG_SUCCESS){ + return SPI_HOST_FLAG_NOT_INIT; + }; + // Configure spi Master<->Slave connection on CSID 0 + configure_spi(); + + // Set CSID + if(spi_set_csid(spi_hst, 0) != SPI_FLAG_SUCCESS){ + return SPI_HOST_FLAG_CSID_INVALID; + }; + + return SPI_FLAG_SUCCESS; // Success +} + + + +static spi_flags_e spi_slave_write(uint32_t addr, uint32_t *data, uint16_t length) { + /* The OBI SPI slave IP has no way of knowing or handling incorrect addresses. + * Using an incorrect address leads to an abort of the simulation and it is + * unkown what happens when this issue occurs after physical implementation. + * This is why it is absolutely necessary to include checks such as the following two lines. + */ + if(DIV_ROUND_UP(length, WORD_SIZE_IN_BYTES) > MAX_DATA_SIZE) return SPI_SLAVE_FLAG_SIZE_OF_DATA_EXCEEDED; + if (addr % 4 != 0 || (addr + length) > LAST_VALID_ADDRESS) return SPI_SLAVE_FLAG_ADDRESS_INVALID; + + + spi_slave_write_wrap_length(length); + + spi_write_byte(spi_hst, WRITE_SPI_SLAVE_CMD); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + ///write address + spi_write_word(spi_hst, make_word_compatible_for_spi_host(addr)); + spi_wait_for_ready(spi_hst); + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); + + + /* + * Place data in TX FIFO + * In simulation it do not wait for the flash to be ready, so we must check + * if the FIFO is full before writing. + */ + uint16_t counter = 0; + uint32_t *data_32bit = (uint32_t *)data; + for (uint16_t i = 0; i < (length>>2 ); i++) { + if(SPI_HOST_PARAM_TX_DEPTH == counter){ + send_command_to_spi_host(SPI_HOST_PARAM_TX_DEPTH*WORD_SIZE_IN_BYTES, true, SPI_DIR_TX_ONLY); + counter = 0; + } + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(data_32bit[i])); + counter++; + } + if (length % 4 != 0) { + uint32_t last_word = 0; + memcpy(&last_word, &data[length - length % 4], length % 4); + spi_wait_for_tx_not_full(spi_hst); + spi_write_word(spi_hst, make_word_compatible_for_spi_host(last_word)); + } + + uint16_t last_words = length-((length/(SPI_HOST_PARAM_TX_DEPTH*4))*(SPI_HOST_PARAM_TX_DEPTH*4)); + + if(last_words == 0){ + last_words = SPI_HOST_PARAM_TX_DEPTH*4; + } + + send_command_to_spi_host(last_words, false, SPI_DIR_TX_ONLY); + spi_wait_for_tx_empty(spi_hst); + return SPI_FLAG_SUCCESS; // Success +} + + +static void configure_spi() { + // Configure spi clock + uint16_t clk_div = 0; + + // Spi Configuration + // Configure chip 0 (slave) + const uint32_t chip_cfg = spi_create_configopts((spi_configopts_t){ + .clkdiv = clk_div, + .csnidle = 0xF, + .csntrail = 0xF, + .csnlead = 0xF, + .fullcyc = false, + .cpha = 0, + .cpol = 0 + }); + spi_set_configopts(spi_hst, 0, chip_cfg); +} + + +/* +* This function is optional since 32 are set by default. +* Also when setting the dummy cycles too low it breaks the +* SPI OBI slave IP since the buffer is too slow to process +* the data. +*/ +void spi_slave_write_dummy_cycles(uint8_t cycles){ + + // Load command to TX FIFO + spi_write_byte(spi_hst, WRITE_SPI_SLAVE_REG_0); + spi_wait_for_ready(spi_hst); + + //The spi_write_byte function fills always the same byte in the TX FIFO of the SPI HOST IP + //Hence it is not possible to write the reg 1 command and cycles at the same time without writing a word. + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); + + // Load command to TX FIFO + spi_write_byte(spi_hst, cycles); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(1, true, SPI_DIR_TX_ONLY); +} + + +void spi_slave_write_wrap_length(uint16_t length){ + + uint32_t wrap_length_cmds = (WRITE_SPI_SLAVE_REG_1 << 24) //Write register 1 + + (LOWER_BYTE_16_BITS(length>>2) << 16) //Wraplength low + + (WRITE_SPI_SLAVE_REG_2 << 8) //Write register 2 + + (UPPER_BYTE_16_BITS(length>>2)); //Wraplength high + + spi_write_word(spi_hst, make_word_compatible_for_spi_host(wrap_length_cmds)); + spi_wait_for_ready(spi_hst); + + send_command_to_spi_host(4, true, SPI_DIR_TX_ONLY); +} + +/* +* This function activates the SPI clock for the specified amount of cycles. +*/ +void spi_host_wait(uint8_t cycles){ + if(cycles == 0){ + return; + } + send_command_to_spi_host(cycles, true, SPI_DIR_DUMMY); +} + +void print_array(const char *label, uint32_t *array, uint16_t size) { + printf("%s: [", label); + for (uint16_t i = 0; i < size>>2; i++) { + printf("%d", array[i]); + if (i < size - 1) { + printf(", "); + printf("]\n"); + } + } + printf("]\n"); +} + +/* +* The SPI Host IP changes the byte order. This helper function changes the byte order sent to the SPI Host IP +* such that the correct byte order will be transmitted to the SPI slave +*/ +uint32_t make_word_compatible_for_spi_host(uint32_t word){ + return (LOWER_BYTE_16_BITS(LOWER_BYTES_32_BITS(word)) << 24) + | (UPPER_BYTE_16_BITS(LOWER_BYTES_32_BITS(word)) << 16) + | (LOWER_BYTE_16_BITS(UPPER_BYTES_32_BITS(word)) << 8) + | UPPER_BYTE_16_BITS(UPPER_BYTES_32_BITS(word)); +} + +/* +* The SPI Host IP also shuffels the byte order when receiving data. +* The following helper function corrects the byte order of the received data. +*/ +void make_compare_data_compatible(uint32_t *compare_data, uint16_t length){ + for(uint16_t i = 0; i < length>>2; i++){ + compare_data[i] = make_word_compatible_for_spi_host(compare_data[i]); + } +} + + +bool test_process(Dataset *dataset, OperationType operation, uint8_t dummy_cycles){ + + spi_return_flags_e flags; + if(operation == OPERATION_WRITE){ + flags = spi_slave_write(dataset->target_data, dataset->test_data, dataset->data_length); + if (flags != SPI_FLAG_SUCCESS){ + printf("Failure to write\n Error code: %d", flags); + return EXIT_FAILURE; + } + if (memcmp(dataset->target_data, dataset->test_data, dataset->data_length/4) != 0) { + printf("Failure to send correct data\n"); + return EXIT_FAILURE; + } + } + else{ + uint32_t *compare_data_read = (uint32_t *)calloc(dataset->data_length/4, sizeof(uint32_t)); + flags = spi_slave_read(dataset->target_data, compare_data_read, dataset->data_length, dummy_cycles); + if (flags != SPI_FLAG_SUCCESS){ + printf("Failure to read\n Error code: %d", flags); + free(compare_data_read); + return EXIT_FAILURE; + } + make_compare_data_compatible(compare_data_read, dataset->data_length); + if (memcmp(dataset->test_data, compare_data_read, dataset->data_length/4) != 0) { + printf("Failure to retrieve correct data\n"); + free(compare_data_read); + return EXIT_FAILURE; + } + free(compare_data_read); + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tb/ext_bus.sv b/tb/ext_bus.sv index 2e43dd146..82943cb3e 100644 --- a/tb/ext_bus.sv +++ b/tb/ext_bus.sv @@ -34,13 +34,13 @@ module ext_bus #( input obi_pkg::obi_req_t heep_debug_master_req_i, output obi_pkg::obi_resp_t heep_debug_master_resp_o, - input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_read_req_i , + input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_read_req_i, output obi_pkg::obi_resp_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_read_resp_o, - input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_write_req_i, + input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_write_req_i, output obi_pkg::obi_resp_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_write_resp_o, - input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_addr_req_i , + input obi_pkg::obi_req_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_addr_req_i, output obi_pkg::obi_resp_t [core_v_mini_mcu_pkg::DMA_NUM_MASTER_PORTS-1:0] heep_dma_addr_resp_o, // External master ports @@ -137,8 +137,9 @@ module ext_bus #( // show writes if requested always_ff @(posedge clk_i, negedge rst_ni) begin : verbose_writes if ($test$plusargs("verbose") != 0 && heep_core_data_req_i.req && heep_core_data_req_i.we) - $display("write addr=0x%08x: data=0x%08x", heep_core_data_req_i.addr, - heep_core_data_req_i.wdata); + $display( + "write addr=0x%08x: data=0x%08x", heep_core_data_req_i.addr, heep_core_data_req_i.wdata + ); end `endif diff --git a/tb/testharness.sv b/tb/testharness.sv index e9a2cd3cf..a465a95f7 100644 --- a/tb/testharness.sv +++ b/tb/testharness.sv @@ -224,10 +224,10 @@ module testharness #( .gpio_11_io(gpio[11]), .gpio_12_io(gpio[12]), .gpio_13_io(gpio[13]), - .gpio_14_io(gpio[14]), - .gpio_15_io(gpio[15]), - .gpio_16_io(gpio[16]), - .gpio_17_io(gpio[17]), + .spi_slave_sck_io(spi_sck), + .spi_slave_cs_io(spi_csb[0]), + .spi_slave_miso_io(spi_sd_io[1]), + .spi_slave_mosi_io(spi_sd_io[0]), .spi_flash_sck_io(spi_flash_sck), .spi_flash_cs_0_io(spi_flash_csb[0]), .spi_flash_cs_1_io(spi_flash_csb[1]), @@ -612,29 +612,7 @@ module testharness #( .i2s_sd_o(gpio[22]) ); -`ifndef VERILATOR - // Flash used for booting (execute from flash or copy from flash) - spiflash flash_boot_i ( - .csb(spi_flash_csb[0]), - .clk(spi_flash_sck), - .io0(spi_flash_sd_io[0]), // MOSI - .io1(spi_flash_sd_io[1]), // MISO - .io2(spi_flash_sd_io[2]), - .io3(spi_flash_sd_io[3]) - ); -`endif -`ifndef VERILATOR - // Flash used as an example device with an SPI interface - spiflash flash_device_i ( - .csb(spi_csb[0]), - .clk(spi_sck), - .io0(spi_sd_io[0]), // MOSI - .io1(spi_sd_io[1]), // MISO - .io2(spi_sd_io[2]), - .io3(spi_sd_io[3]) - ); -`endif if ((core_v_mini_mcu_pkg::CpuType == cv32e40x || core_v_mini_mcu_pkg::CpuType == cv32e40px) && X_EXT != 0) begin: gen_fpu_ss_wrapper fpu_ss_wrapper #(