888 888 888 888b d888 .d88888b. .d8888b. 8888888b. 8888888 888 888 888 8888b d8888 d88P" "Y88b d88P Y88b 888 Y88b 888 888 888 888 88888b.d88888 888 888 Y88b. 888 888 888 888 Y88b d88P 888Y88888P888 888 888 "Y888b. 888 d88P 888 888 Y88b d88P 888 Y888P 888 888 888 "Y88b. 8888888P" 888 888 Y88o88P 888 Y8P 888 888 Y8b 888 "888 888 888 888 Y888P 888 " 888 Y88b.Y8b88P Y88b d88P 888 888 88888888 Y8P 888 888 "Y888888" "Y8888P" 888 8888888 Y8b 21 June 2026 - Introduction - This VIP is at an early stage, will release more version in coming weeks. - Test: - lvm_qspi_01_basic_test - Demonstrate the packet sent with various configuration of lanes per field - lvm_qspi_02_omit_test - Demonstrate the packet incomplete fields within a packet - lvm_qspi_03_cpol_cpha_test - Demonstrate the cpol and cpha, for a simple write followed by read - lvm_qspi_04_write_read_erase_test - Demonstrate erase APIs and their effect. - The self verification testbench consisted of: 1 active master VIP calls .write / .read 1 passive monitor VIP can observe the same bus if enabled 1 slave / flash-side VIP can preload read-return bytes for master reads - TODO: Next in release - Testsuite of random read and write - VIP configuration - [master and slave] Traffic configuration - CPOL, CPHA control the sampling and driving edge, per the diagram below: - {.cfg.cpol, .cfg.cpha} CSN# ──────┐______________________________________________________┌────── SCK (0,0) ___________/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\___________ SCK (0,1) ________/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\______________ SCK (1,0) ‾‾‾‾‾‾‾‾‾‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾‾‾‾‾‾‾‾‾‾ SCK (1,1) ‾‾‾‾‾‾‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾\__/‾‾‾‾‾‾‾‾‾‾‾‾‾‾ IO [ D7 ][ D6 ][ D5 ][ D4 ][ D3 ][ D2 ][ D1 ][ D0 ] | | | | | | | | - .cfg.mode - Supported values: - LVM_QSPI - QSPI protocol - LVM_QSPI_SPI - Classic SPI protocol - .cfg.instr_lanes - Lane width used during instruction phase. - Supported values: - LVM_QSPI_1_LANE - LVM_QSPI_2_LANES - LVM_QSPI_4_LANES - .cfg.addr_lanes - Lane width used during address phase. - Supported values: - LVM_QSPI_1_LANE - LVM_QSPI_2_LANES - LVM_QSPI_4_LANES - .cfg.addr_nbytes - Default address width in bytes. - integer value supported - .cfg.alt_lanes - Lane width used during alternate-byte phase. - Supported values: - LVM_QSPI_1_LANE - LVM_QSPI_2_LANES - LVM_QSPI_4_LANES - .cfg.alt_nbytes - Default alternate-byte width in bytes. - Support 0.5 (4 bits / nibble of byte), 1, 2, .... - .cfg.dummy_cycles - Default number of dummy SCLK cycles before data phase. - integer value supported - .cfg.data_lanes - Lane width used during data phase. - Supported values: - LVM_QSPI_1_LANE - LVM_QSPI_2_LANES - LVM_QSPI_4_LANES - .cfg.idle_lane_is_z - 1: Unused lanes will be 'z - 0: Unused lanes will be '0 - .copy_cfg - Handy API to let user copy configurations from 1 instance to another - instr_lanes - addr_lanes & addr_nbytes - alt_lanes & alt_nbytes - dummy_cycles - data_lanes - cpol - cpha - idle_lane_is_z - mode - mem_type - wren - sector_size - page_size - Example usage: - .copy_cfg() - This will copy from A to B - .add_instruction - This api load the VIP instance for the instructions database and its properties - Properties: - instruction : the 8 bits value of the instruction - name : name of the instruction - dir : direction of the data field: LVM_QSPI_WRITE / LVM_QSPI_READ - has_addr : this packet has address? - has_alt : this packet has alt byte? - has_dummy : this packet has dummy cycles? - has_data : this packet has data fields - purpose : How it impacts: - LVM_QSPI_WRITE_MEM : write to memory - LVM_QSPI_READ_MEM : read from memory - LVM_QSPI_ERASE_ALL : full erase memory - LVM_QSPI_ERASE_PAGE : page erase memory - LVM_QSPI_ERASE_SECTOR : sector erase memory - LVM_QSPI_WRITE_ENABLE : Makes memory writable - LVM_QSPI_WRITE_DISABLE : Makes memory not writable - LVM_QSPI_READ_STATUS_REG : Read status register (TODO NEXT) - Example usage: m_qspi_env.add_instruction( .instruction ( 8'h03 ), .name ( "MEM_READ" ), .purpose ( LVM_QSPI_READ_MEM ), .dir ( LVM_QSPI_READ ), .has_addr ( 1'b1 ), .has_alt ( 1'b1 ), .has_dummy ( 1'b1 ), .has_data ( 1'b1 ), ); m_qspi_env.add_instruction( .instruction ( 8'h04 ), .name ( "MEM_MASS_ERASE" ), .purpose ( LVM_QSPI_ERASE_ALL), .dir ( LVM_QSPI_WRITE ), .has_addr ( 1'b0 ), .has_alt ( 1'b0 ), .has_dummy ( 1'b0 ), .has_data ( 1'b0 ) ); - .copy_instruction_set - Handy API to let user copy instruction database from 1 instance to another - Example usage: - .copy_instruction_set() - This will copy from A to B - .cfg.wren - At master/passive, it keeps track the slave on the bus can be written or not to calculate expected data - At slave , it decides incoming write / erase will take effect or not - Possible values: - LVM_QSPI_WRITE_DISABLED: Cannot be written <-- Default - can be programmed by "LVM_QSPI_WRITE_DISABLE" packet - LVM_QSPI_WRITE_ENABLED : Can be written - can be programmed by "LVM_QSPI_WRITE_ENABLE" packet - .cfg.sector_size - Sector size is default to 128*1024, which is 128 KBytes = 20000h - .cfg.page_size - Page size is default to 1*1024, which is 1 KBytes = 400h - .cfg.mode - Determine the mode of the VIP, supported values are: - LVM_QSPI (standard qspi) - All fields support 1/2/4 lanes - LVM_QSPI_SPI (classic spi ) - Data fields support 1 lane only (will be uvm_fatal if configure 2 or 4 lanes) - .cfg.mem_type - Determine how the memory element works - Possible values: - LVM_QSPI_NORMAL_MEM - Normal memory - LVM_QSPI_FLASH_MEM - Flash memory: Only 1-->0 transitions work - [master only] Timing related cfg - .cfg.sclk_period - Full SCLK period driven by the master. - No need to configure for passive / slave instances - .cfg.sclk_half_period - Derived half period used by the driver while pulsing SCLK. - No need to be set by user - .cfg.t_css - Chip-select setup time before first clock edge. - Default = 2ns - .cfg.t_csh - Chip-select hold / inactive time around transfers. - Default = 2ns Sending traffic - 2 categories of API, where: - read : data is flowing from slave to master - write: data is flowing from master to slave - packet without data element shall use write API also - .write - Send a "write" packet where data will flow from master to slave, if there is data field. - INPUT - instr : Instruction to be transmitted - name : Instruction name to be transmitted - addr : Address payload - tx_nbytes : Number of bytes to be sent - data[] : Data bytes to be written, same rules like addr apply - alt : Alternate-byte field , same rules like addr apply - Note: - Either instr or name must be supplied - For addr, alt, rules as below: - if applicable and supplied , then will be used to drive - if applicable and not supplied, then it will be randomized - if not applicable, the value is ignored - User can supply control the write data (if applicable) by: - Just setting tx_nbytes Then random data of tx_nbytes will be sent - Just supplying data array Then the value of data array will be sent - Without supplying tx_nbytes nor data array Then random data of length randomized from 1 to cfg.max_tx_nbytes will be sent - Example API usage 1 logic [7:0] tx_data[]; tx_data = new[4]; tx_data[0] = 8'h11; tx_data[1] = 8'h22; tx_data[2] = 8'h33; tx_data[3] = 8'h44; m_qspi_env.write( .instr (8'h02 ), .addr (32'h0000_1000), .data (tx_data ), .alt (32'h0000_00A5) ); - Example API usage 2 m_qspi_env.write( .name ("MEM_MASS_ERASE") ); - .read - Send a read-style QSPI packet and return the captured bytes. - INPUT - instr, addr, alt, name: Same description of write API - rx_nbytes : Total bytes to read - OUTPUT - data[] : Captured read bytes, output cannot be skipped - Example API usage logic [7:0] rx_data[]; m_qspi_env.read( .instr (8'h03 ), .rx_nbytes (4 ), .addr (32'h0000_1000), .alt (32'h0000_005A), .data (rx_data ) ); - The rx_data argument is fetching the returned data from slave - can be printed via: `uvm_info(msg_tag, $sformatf("Read returns %p", rx_data), UVM_NONE) - Slave - .cfg.read_source - Select where read bytes come from. - Common values: - LVM_QSPI_READ_FROM_MEMORY (default) - return data from previously loaded data via frontdoor write or memory backdoor deposit - LVM_QSPI_READ_FROM_QUEUE - return data from user prepared queue - LVM_QSPI_READ_RANDOM - return random data - .cfg.rand_read_data - If queued read data is exhausted, random fallback data can still be returned. - .add_read_data - Queue one byte for subsequent read response. - Typically used on the slave / flash-side VIP before the master calls read. - Example: s_qspi_env.add_read_data(8'h77); s_qspi_env.add_read_data(8'h88); s_qspi_env.add_read_data(8'h99); s_qspi_env.add_read_data(8'hAA); - .clear_read_data - Clear all previously staged read bytes. - Useful before preparing a fresh read scenario. - Example: s_qspi_env.clear_read_data(); - Debug signals - current_frame : Current field in the frame, for example: INSTRUCTION, ADDR, etc - byte_count : Counting the total bytes sent / received, start from "1" - bit_count_0 : Lane 0, counting bits - bit_count_1 : Lane 1, counting bits - bit_count_2 : Lane 2, counting bits - bit_count_3 : Lane 3, counting bits - instruction : Instruction value captured - address : Address value captured - alt : ALT byte value captured - data : Data value captured, per byte - ev_read_port : A read qspi packet - ev_write_port : A write qspi packet - ev_data_check : Data check is done - ev_returning_data : Slave is returning data - ev_mon_sampling : Monitor is sampling data - driving_edge : Data driving at which clock edge - sampling_edge : Data sampling at which clock edge - Integration steps: - Connection files demonstrating module world step: interface, and connections lvm_qspi/tb/lvm_m_qspi_connection.sv lvm_qspi/tb/lvm_p_qspi_connection.sv lvm_qspi/tb/lvm_s_qspi_connection.sv - Dynamic world, base test is always the best referrence lvm_qspi/tests/lvm_qspi_base_test.sv - Seq item retrieval - User can refer to the code at: lvm_qspi/tb/example_qspi_user_sbd.sv - Sequence item's print_qspi API is showing all useful variables that can be printed out - Practical notes - If user changes lane cfg on the master during runtime, update the other participating QSPI VIP instances as well, via copy_cfg API - Use clear_read_data before loading a new expected read payload to avoid stale bytes from previous read. - Use read_source == LVM_QSPI_READ_FROM_QUEUE when user wants deterministic read return data from the slave-side VIP. - If user define the instance with memory range, it will activatememory checker at master, and functions as memory storage at slave. - anything outside the range, will be ignored.