Add chip_sw_spi_host_tx_rx and 2 other tests to edacloud Change-Id: Ida365559c0b3bb280456fcb9bac6199c0ee22f74
diff --git a/hw/top_matcha/dv/chip_sim_cfg.hjson b/hw/top_matcha/dv/chip_sim_cfg.hjson index 3a4964e..cc64188 100644 --- a/hw/top_matcha/dv/chip_sim_cfg.hjson +++ b/hw/top_matcha/dv/chip_sim_cfg.hjson
@@ -808,13 +808,13 @@ { name: chip_sw_spi_host_tx_rx uvm_test_seq: chip_sw_spi_host_tx_rx_vseq - sw_images: ["//sw/device/tests/sim_dv:spi_host_tx_rx_test:1"] + sw_images: ["//sw/device/tests/sim_dv:spi_host_tx_rx_test:1:matcha"] en_run_modes: ["sw_test_mode_test_rom"] } { name: chip_sw_spi_device_pass_through uvm_test_seq: chip_sw_spi_passthrough_vseq - sw_images: ["//sw/device/tests/sim_dv:spi_passthrough_test:1"] + sw_images: ["//sw/device/tests/sim_dv:spi_passthrough_test:1:matcha"] en_run_modes: ["sw_test_mode_test_rom"] } { @@ -1981,7 +1981,7 @@ { name: chip_sw_ast_clk_rst_inputs uvm_test_seq: chip_sw_ast_clk_rst_inputs_vseq - sw_images: ["//sw/device/tests/sim_dv:ast_clk_rst_inputs:1"] + sw_images: ["//sw/device/tests/sim_dv:ast_clk_rst_inputs:1:matcha"] en_run_modes: ["sw_test_mode_test_rom"] run_opts: ["+sw_test_timeout_ns=200_000_000"] } @@ -2044,6 +2044,7 @@ "chip_sw_smc_supervisor_mode_test", "chip_sw_smc_virtual_memory_test", "chip_sw_spi_device_tx_rx", + "chip_sw_spi_host_tx_rx", "chip_sw_sram_ctrl_smoketest", "chip_sw_uart_smoketest", "chip_sw_uart_tx_rx", @@ -2060,6 +2061,7 @@ "chip_sw_alert_handler_ping_timeout", "chip_sw_alert_test", "chip_sw_aon_timer_smoketest", + "chip_sw_ast_clk_rst_inputs", "chip_sw_cam_ctrl_test", "chip_sw_clkmgr_off_peri", "chip_sw_clkmgr_smoketest", @@ -2115,7 +2117,9 @@ "chip_sw_smc_rv_timer_test", "chip_sw_smc_supervisor_mode_test", "chip_sw_smc_virtual_memory_test", + "chip_sw_spi_device_pass_through", "chip_sw_spi_device_tx_rx", + "chip_sw_spi_host_tx_rx", "chip_sw_sram_ctrl_smoketest", "chip_sw_uart_smoketest", "chip_sw_uart_tx_rx", @@ -2134,6 +2138,7 @@ "chip_sw_alert_handler_ping_timeout", "chip_sw_alert_test", "chip_sw_aon_timer_smoketest", + "chip_sw_ast_clk_rst_inputs", "chip_sw_atomics_test", "chip_sw_cam_ctrl_test", "chip_sw_clkmgr_off_peri", @@ -2201,7 +2206,9 @@ "chip_sw_smc_supervisor_mode_test", "chip_sw_smc_tlul_mailbox_test", "chip_sw_smc_virtual_memory_test", + "chip_sw_spi_device_pass_through", "chip_sw_spi_device_tx_rx", + "chip_sw_spi_host_tx_rx", "chip_sw_sram_ctrl_smoketest", "chip_sw_uart_smoketest", "chip_sw_uart_tx_rx",
diff --git a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv index 392e9f4..e67a4f9 100644 --- a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv +++ b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv
@@ -1,3 +1,4 @@ +// Copyright 2023 Google LLC. // Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 @@ -57,6 +58,10 @@ // enable spi agent cfg.chip_vif.enable_spi_device(.inst_num(spi_host_idx), .enable(1)); + // Wait until we reach the SW test state. + `DV_WAIT(cfg.sw_test_status_vif.sw_test_status == SwTestStatusInTest) + cfg.chip_vif.gpios_if.disconnect(); + // Wait for the sw to finish configuring the spi_host DUT `DV_WAIT(cfg.sw_logger_vif.printed_log == "spi host configuration complete", "Timedout waiting for spi host c configuration.")
diff --git a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv index 7b5b905..a4d10cc 100644 --- a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv +++ b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv
@@ -1,3 +1,4 @@ +// Copyright 2023 Google LLC. // Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 @@ -158,6 +159,8 @@ `DV_WAIT(cfg.sw_test_status_vif.sw_test_status == SwTestStatusInTest, "Timed out waiting for SwTestStatusInTest", 10_000_000) + cfg.chip_vif.gpios_if.disconnect(); + `DV_WAIT(cfg.sw_logger_vif.printed_log == "Test setup complete.", "Timeout waiting for software to complete setup.")
diff --git a/sw/device/lib/testing/pinmux_testutils.c b/sw/device/lib/testing/pinmux_testutils.c index d71a23e..8a058b2 100644 --- a/sw/device/lib/testing/pinmux_testutils.c +++ b/sw/device/lib/testing/pinmux_testutils.c
@@ -106,3 +106,16 @@ } uint32_t pinmux_testutils_get_testable_gpios_mask(void) { return 0xffffffff; } + +void pinmux_testutils_configure_pads(const dif_pinmux_t *pinmux, + const pinmux_pad_attributes_t *attrs, + size_t num_attrs) { + for (size_t i = 0; i < num_attrs; ++i) { + dif_pinmux_pad_attr_t desired_attr, actual_attr; + CHECK_DIF_OK(dif_pinmux_pad_get_attrs(pinmux, attrs[i].pad, attrs[i].kind, + &desired_attr)); + desired_attr.flags = attrs[i].flags; + CHECK_DIF_OK(dif_pinmux_pad_write_attrs(pinmux, attrs[i].pad, attrs[i].kind, + desired_attr, &actual_attr)); + } +}
diff --git a/sw/device/lib/testing/pinmux_testutils.h b/sw/device/lib/testing/pinmux_testutils.h index 6b401b4..5028683 100644 --- a/sw/device/lib/testing/pinmux_testutils.h +++ b/sw/device/lib/testing/pinmux_testutils.h
@@ -33,4 +33,20 @@ */ uint32_t pinmux_testutils_get_testable_gpios_mask(void); +/** + * A convenience struct to associate pad attributes with a specific pad. + */ +typedef struct pinmux_pad_attributes { + dif_pinmux_index_t pad; + dif_pinmux_pad_kind_t kind; + dif_pinmux_pad_attr_flags_t flags; +} pinmux_pad_attributes_t; + +/** + * Configures several pad attributes. + */ +void pinmux_testutils_configure_pads(const dif_pinmux_t *pinmux, + const pinmux_pad_attributes_t *attrs, + size_t num_attrs); + #endif // SW_DEVICE_LIB_TESTING_PINMUX_TESTUTILS_H_
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD index 8c516e3..99a1de3 100644 --- a/sw/device/tests/sim_dv/BUILD +++ b/sw/device/tests/sim_dv/BUILD
@@ -181,6 +181,57 @@ ], ) +matcha_dv_test( + name = "ast_clk_rst_inputs", + srcs = ["ast_clk_rst_inputs.c"], + deps = [ + "//sw/device/lib/dif:alert_handler", + "//sw/device/lib/dif:clkmgr", + "//sw/device/lib/dif:rstmgr", + "//sw/device/lib/dif:rv_plic_sec", + "//sw/device/lib/testing:clkmgr_testutils", + "//sw/device/lib/testing:isr_testutils", + "//sw/device/tests:test_dv_lib", + "@lowrisc_opentitan//sw/device/lib/dif:aon_timer", + "@lowrisc_opentitan//sw/device/lib/dif:entropy_src", + "@lowrisc_opentitan//sw/device/lib/dif:pwrmgr", + "@lowrisc_opentitan//sw/device/lib/dif:sensor_ctrl", + "@lowrisc_opentitan//sw/device/lib/testing:alert_handler_testutils", + "@lowrisc_opentitan//sw/device/lib/testing:aon_timer_testutils", + "@lowrisc_opentitan//sw/device/lib/testing:pwrmgr_testutils", + "@lowrisc_opentitan//sw/device/lib/testing:rstmgr_testutils", + "@lowrisc_opentitan//sw/device/lib/testing:rv_plic_testutils", + ], +) + +matcha_dv_test( + name = "spi_host_tx_rx_test", + srcs = ["spi_host_tx_rx_test.c"], + deps = [ + "//sw/device/lib/dif:pinmux", + "//sw/device/lib/testing:pinmux_testutils", + "//sw/device/tests:test_dv_lib", + "@lowrisc_opentitan//sw/device/lib/dif:spi_host", + "@lowrisc_opentitan//sw/device/lib/testing:rand_testutils", + ], +) + +matcha_dv_test( + name = "spi_passthrough_test", + srcs = ["spi_passthrough_test.c"], + deps = [ + "//sw/device/lib/dif:pinmux", + "//sw/device/lib/dif:rv_plic_sec", + "//sw/device/lib/testing:pinmux_testutils", + "//sw/device/tests:test_dv_lib", + "@lowrisc_opentitan//sw/device/lib/base:bitfield", + "@lowrisc_opentitan//sw/device/lib/dif:spi_device", + "@lowrisc_opentitan//sw/device/lib/dif:spi_host", + "@lowrisc_opentitan//sw/device/lib/testing:spi_device_testutils", + "@lowrisc_opentitan//sw/device/lib/testing:spi_flash_testutils", + ], +) + ################################################################################ # Place the Opentitan-sourced DV test below. # # Opentitan DV binary build flow does not support centOS7, so we pull the #
diff --git a/sw/device/tests/sim_dv/ast_clk_rst_inputs.c b/sw/device/tests/sim_dv/ast_clk_rst_inputs.c new file mode 100644 index 0000000..7f863db --- /dev/null +++ b/sw/device/tests/sim_dv/ast_clk_rst_inputs.c
@@ -0,0 +1,578 @@ +// Copyright 2023 Google LLC. +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "sensor_ctrl_regs.h" +#include "sw/device/lib/base/memory.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_alert_handler.h" +#include "sw/device/lib/dif/dif_aon_timer.h" +#include "sw/device/lib/dif/dif_clkmgr.h" +#include "sw/device/lib/dif/dif_entropy_src.h" +#include "sw/device/lib/dif/dif_rv_plic.h" +#include "sw/device/lib/dif/dif_sensor_ctrl.h" +#include "sw/device/lib/runtime/ibex.h" +#include "sw/device/lib/runtime/irq.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/alert_handler_testutils.h" +#include "sw/device/lib/testing/aon_timer_testutils.h" +#include "sw/device/lib/testing/autogen/isr_testutils.h" +#include "sw/device/lib/testing/clkmgr_testutils.h" +#include "sw/device/lib/testing/pwrmgr_testutils.h" +#include "sw/device/lib/testing/rstmgr_testutils.h" +#include "sw/device/lib/testing/rv_plic_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" + +#define kAlertSet true +#define kAlertClear false +#define kAlertVal7 7 +#define kAlertVal8 8 +#define kDifNoWakeup 0 + +OTTF_DEFINE_TEST_CONFIG(); + +static volatile const uint8_t kNumLowPowerSamples; +static volatile const uint8_t kNumNormalPowerSamples; +static volatile const uint8_t kWakeUpTimeInUs; + +static volatile const uint8_t kChannel0MaxLowByte; +static volatile const uint8_t kChannel0MaxHighByte; +static volatile const uint8_t kChannel0MinLowByte; +static volatile const uint8_t kChannel0MinHighByte; + +static volatile const uint8_t kChannel1MaxLowByte; +static volatile const uint8_t kChannel1MaxHighByte; +static volatile const uint8_t kChannel1MinLowByte; +static volatile const uint8_t kChannel1MinHighByte; + +static const uint32_t kPlicTarget = kTopMatchaPlicTargetIbex0; + +static dif_sensor_ctrl_t sensor_ctrl; +static dif_alert_handler_t alert_handler; +static dif_aon_timer_t aon_timer; +static dif_rv_plic_t rv_plic; +static dif_rv_plic_t plic; +static dif_pwrmgr_t pwrmgr; +static dif_rstmgr_t rstmgr; +static dif_entropy_src_t entropy_src; +static dif_pwrmgr_wakeup_reason_t wakeup_reason; + +static dif_clkmgr_t clkmgr; +static dif_adc_ctrl_t adc_ctrl; + +static volatile bool interrupt_serviced = false; +static bool first_adc_setup = true; + +static plic_isr_ctx_t plic_ctx = {.rv_plic = &plic, + .hart_id = kTopMatchaPlicTargetIbex0}; + +static pwrmgr_isr_ctx_t pwrmgr_isr_ctx = { + .pwrmgr = &pwrmgr, + .plic_pwrmgr_start_irq_id = kTopMatchaPlicIrqIdPwrmgrAonWakeup, + .expected_irq = kDifPwrmgrIrqWakeup, + .is_only_irq = true}; + +enum { + /** + * The size of the buffer used in firmware to process the entropy bits in + * firmware override mode. + */ + kEntropyFifoBufferSize = 32, +}; + +enum { + kPowerUpTimeInUs = 30, +}; + +static uint32_t read_fifo_depth(dif_entropy_src_t *entropy) { + uint32_t fifo_depth = 0; + CHECK_DIF_OK(dif_entropy_src_get_fifo_depth(entropy, &fifo_depth)); + return fifo_depth; +} + +static uint32_t get_events(dif_toggle_t fatal) { + dif_sensor_ctrl_events_t events = 0; + if (dif_toggle_to_bool(fatal)) { + CHECK_DIF_OK(dif_sensor_ctrl_get_fatal_events(&sensor_ctrl, &events)); + } else { + CHECK_DIF_OK(dif_sensor_ctrl_get_recov_events(&sensor_ctrl, &events)); + } + return events; +} + +/** + * Clear event trigger and recoverable status. + */ +static void clear_event(uint32_t idx, dif_toggle_t fatal) { + CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx, + kDifToggleDisabled)); + if (!dif_toggle_to_bool(fatal)) { + CHECK_DIF_OK(dif_sensor_ctrl_clear_recov_event(&sensor_ctrl, idx)); + } +} + +/** + * Check alert cause registers are correctly set + */ +static void check_alert_state(dif_toggle_t fatal) { + bool fatal_cause = false; + bool recov_cause = false; + + CHECK_DIF_OK(dif_alert_handler_alert_is_cause( + &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert, &fatal_cause)); + + CHECK_DIF_OK(dif_alert_handler_alert_is_cause( + &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert, &recov_cause)); + + if (dif_toggle_to_bool(fatal)) { + CHECK(fatal_cause & !recov_cause, + "Fatal alert not correctly observed in alert handler"); + } else { + CHECK(recov_cause & !fatal_cause, + "Recov alert not correctly observed in alert handler"); + } + + CHECK_DIF_OK(dif_alert_handler_alert_acknowledge( + &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert)); + CHECK_DIF_OK(dif_alert_handler_alert_acknowledge( + &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert)); +}; + +/** + * First configure fatality of the desired event. + * Then trigger the event from sensor_ctrl to ast. + * Next poll for setting of correct events inside sensor_ctrl status. + * When a recoverable event is triggerd, make sure only recoverable + * status is seen, likewise for fatal events. + * Finally, check for correct capture of cause in alert handler. + */ +static void test_event(uint32_t idx, dif_toggle_t fatal, bool set_event) { + if (set_event) { + // Configure event fatality + CHECK_DIF_OK(dif_sensor_ctrl_set_alert_fatal(&sensor_ctrl, idx, fatal)); + + // Trigger event + CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx, + kDifToggleEnabled)); + // wait for events to set + IBEX_SPIN_FOR(get_events(fatal) > 0, 1); + + // Check for the event in ast sensor_ctrl + // if the event is not set, error + CHECK(((get_events(fatal) >> idx) & 0x1) == 1, + "Event %d not observed in AST", idx); + + // check the opposite fatality setting, should not be set + CHECK(((get_events(!fatal) >> idx) & 0x1) == 0, + "Event %d observed in AST when it should not be", idx); + } else { + // clear event trigger + clear_event(idx, fatal); + + // check whether alert handler captured the event + check_alert_state(fatal); + } +}; + +void init_units() { + CHECK_DIF_OK(dif_pwrmgr_init( + mmio_region_from_addr(TOP_MATCHA_PWRMGR_AON_BASE_ADDR), &pwrmgr)); + CHECK_DIF_OK(dif_rstmgr_init( + mmio_region_from_addr(TOP_MATCHA_RSTMGR_AON_BASE_ADDR), &rstmgr)); + CHECK_DIF_OK(dif_entropy_src_init( + mmio_region_from_addr(TOP_MATCHA_ENTROPY_SRC_BASE_ADDR), &entropy_src)); + CHECK_DIF_OK(dif_aon_timer_init( + mmio_region_from_addr(TOP_MATCHA_AON_TIMER_AON_BASE_ADDR), &aon_timer)); + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic)); + CHECK_DIF_OK(dif_sensor_ctrl_init( + mmio_region_from_addr(TOP_MATCHA_SENSOR_CTRL_BASE_ADDR), &sensor_ctrl)); + CHECK_DIF_OK(dif_alert_handler_init( + mmio_region_from_addr(TOP_MATCHA_ALERT_HANDLER_BASE_ADDR), + &alert_handler)); + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &plic)); + CHECK_DIF_OK(dif_clkmgr_init( + mmio_region_from_addr(TOP_MATCHA_CLKMGR_AON_BASE_ADDR), &clkmgr)); + CHECK_DIF_OK(dif_adc_ctrl_init( + mmio_region_from_addr(TOP_MATCHA_ADC_CTRL_AON_BASE_ADDR), &adc_ctrl)); +} + +/** + * configure adc module + */ +static void configure_adc_ctrl(const dif_adc_ctrl_t *adc_ctrl) { + int wake_up_time_aon_cycles; + int power_up_time_aon_cycles; + + power_up_time_aon_cycles = + aon_timer_testutils_get_aon_cycles_from_us(kPowerUpTimeInUs); + wake_up_time_aon_cycles = + aon_timer_testutils_get_aon_cycles_from_us(kWakeUpTimeInUs); + CHECK_DIF_OK(dif_adc_ctrl_set_enabled(adc_ctrl, kDifToggleDisabled)); + CHECK_DIF_OK(dif_adc_ctrl_reset(adc_ctrl)); + CHECK_DIF_OK(dif_adc_ctrl_configure( + adc_ctrl, (dif_adc_ctrl_config_t){ + .mode = kDifAdcCtrlLowPowerScanMode, + .num_low_power_samples = kNumLowPowerSamples, + .num_normal_power_samples = kNumNormalPowerSamples, + .power_up_time_aon_cycles = power_up_time_aon_cycles, + .wake_up_time_aon_cycles = wake_up_time_aon_cycles})); +} + +static void en_plic_irqs(dif_rv_plic_t *plic) { + top_matcha_plic_irq_id_t plic_irqs[] = { + kTopMatchaPlicIrqIdAdcCtrlAonMatchDone}; + + for (uint32_t i = 0; i < ARRAYSIZE(plic_irqs); ++i) { + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + plic, plic_irqs[i], kTopMatchaPlicTargetIbex0, kDifToggleEnabled)); + + // Assign a default priority + CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, plic_irqs[i], 0x1)); + } + + // Enable the external IRQ at Ibex. + irq_global_ctrl(true); + irq_external_ctrl(true); +} + +void adc_setup(bool first_adc_setup) { + // Enable adc interrupts. + CHECK_DIF_OK(dif_adc_ctrl_irq_set_enabled(&adc_ctrl, kDifAdcCtrlIrqMatchDone, + kDifToggleEnabled)); + + uint16_t channel0_filter0_max = + (kChannel0MaxHighByte << 8) | kChannel0MaxLowByte; + uint16_t channel0_filter0_min = + (kChannel0MinHighByte << 8) | kChannel0MinLowByte; + uint16_t channel1_filter0_max = + (kChannel1MaxHighByte << 8) | kChannel1MaxLowByte; + uint16_t channel1_filter0_min = + (kChannel1MinHighByte << 8) | kChannel1MinLowByte; + + if (first_adc_setup) { + // Setup ADC configuration. + configure_adc_ctrl(&adc_ctrl); + } else { + CHECK_DIF_OK(dif_adc_ctrl_reset(&adc_ctrl)); + } + + en_plic_irqs(&plic); + // Setup ADC filters. There is one filter for each channel. + CHECK_DIF_OK(dif_adc_ctrl_configure_filter( + &adc_ctrl, kDifAdcCtrlChannel0, + (dif_adc_ctrl_filter_config_t){.filter = kDifAdcCtrlFilter0, + .generate_irq_on_match = true, + .generate_wakeup_on_match = true, + .in_range = true, + .max_voltage = channel0_filter0_max, + .min_voltage = channel0_filter0_min}, + kDifToggleDisabled)); + CHECK_DIF_OK(dif_adc_ctrl_configure_filter( + &adc_ctrl, kDifAdcCtrlChannel1, + (dif_adc_ctrl_filter_config_t){.filter = kDifAdcCtrlFilter0, + .generate_irq_on_match = true, + .generate_wakeup_on_match = true, + .in_range = true, + .max_voltage = channel1_filter0_max, + .min_voltage = channel1_filter0_min}, + kDifToggleDisabled)); + + // enable filters. + CHECK_DIF_OK(dif_adc_ctrl_filter_set_enabled( + &adc_ctrl, kDifAdcCtrlChannel0, kDifAdcCtrlFilter0, kDifToggleEnabled)); + CHECK_DIF_OK(dif_adc_ctrl_filter_set_enabled( + &adc_ctrl, kDifAdcCtrlChannel1, kDifAdcCtrlFilter0, kDifToggleEnabled)); + + if (first_adc_setup) { + CHECK_DIF_OK(dif_adc_ctrl_set_enabled(&adc_ctrl, kDifToggleEnabled)); + } +} + +void entropy_config(dif_entropy_src_config_t entropy_src_config) { + CHECK_DIF_OK(dif_entropy_src_set_enabled(&entropy_src, kDifToggleDisabled)); + + const dif_entropy_src_fw_override_config_t fw_override_config = { + .entropy_insert_enable = true, + .buffer_threshold = kEntropyFifoBufferSize, + }; + + CHECK_DIF_OK(dif_entropy_src_fw_override_configure( + &entropy_src, fw_override_config, kDifToggleEnabled)); + + CHECK_DIF_OK(dif_entropy_src_configure(&entropy_src, entropy_src_config, + kDifToggleEnabled)); +} + +void ast_enter_sleep_states_and_check_functionality( + dif_pwrmgr_domain_config_t pwrmgr_config, + dif_entropy_src_config_t entropy_src_config, uint32_t alert_idx) { + bool deepsleep; + int read_fifo_depth_val; + bool clock_check_status; + uint32_t unhealthy_fifos, errors, alerts; + + const dif_edn_t edn0 = {.base_addr = + mmio_region_from_addr(TOP_MATCHA_EDN0_BASE_ADDR)}; + const dif_edn_t edn1 = {.base_addr = + mmio_region_from_addr(TOP_MATCHA_EDN1_BASE_ADDR)}; + + if ((pwrmgr_config & (~kDifPwrmgrDomainOptionUsbClockInActivePower)) == 0) { + deepsleep = true; + } else { + deepsleep = false; + } + + if (pwrmgr_testutils_is_wakeup_reason(&pwrmgr, kDifNoWakeup)) { + entropy_config(entropy_src_config); + + // Verify that the FIFO depth is non-zero via SW - indicating the reception + // of data over the AST RNG interface. + IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000); + + // test recoverable event + test_event(alert_idx, kDifToggleDisabled, kAlertSet); + + // Enable all the AON interrupts used in this test. + rv_plic_testutils_irq_range_enable(&rv_plic, kTopMatchaPlicTargetIbex0, + kTopMatchaPlicIrqIdAdcCtrlAonMatchDone, + kTopMatchaPlicIrqIdAdcCtrlAonMatchDone); + CHECK_DIF_OK(dif_pwrmgr_irq_set_enabled(&pwrmgr, 0, kDifToggleEnabled)); + + // Setup low power. + rstmgr_testutils_pre_reset(&rstmgr); + + if (!deepsleep) { + // read fifo depth before enter sleep mode + read_fifo_depth_val = read_fifo_depth(&entropy_src); + } + + // Configure ADC + adc_setup(first_adc_setup); + + // set sleep mode + pwrmgr_testutils_enable_low_power(&pwrmgr, kDifPwrmgrWakeupRequestSourceTwo, + pwrmgr_config); + + // Enter low power mode. + LOG_INFO("Issued WFI to enter sleep."); + + wait_for_interrupt(); + + // Interrupt should have been serviced. + CHECK(interrupt_serviced); + + interrupt_serviced = false; + + } else if (pwrmgr_testutils_is_wakeup_reason( + &pwrmgr, kDifPwrmgrWakeupRequestSourceTwo)) { + if (deepsleep) { + if (read_fifo_depth(&entropy_src) != 0) + LOG_ERROR("read_fifo_depth after reset=%0d should be 0", + read_fifo_depth(&entropy_src)); + + first_adc_setup = false; + // Configure ADC after deep sleep + adc_setup(first_adc_setup); + } + + entropy_config(entropy_src_config); + + IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000); + } + + if (!deepsleep) { + if (read_fifo_depth_val >= read_fifo_depth(&entropy_src)) + LOG_ERROR( + "read_fifo_depth after exit from idle=%0d should be equal/greater " + "than previous read value (%0d)", + read_fifo_depth(&entropy_src), read_fifo_depth_val); + } + + IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000); + + test_event(alert_idx, kDifToggleDisabled, kAlertClear); + + CHECK_DIF_OK(dif_pwrmgr_wakeup_reason_clear(&pwrmgr)); + + // test event after exit from low power + test_event(alert_idx, kDifToggleDisabled, kAlertSet); + test_event(alert_idx, kDifToggleDisabled, kAlertClear); + + // verify there are no any edn alerts/errors + CHECK_DIF_OK(dif_edn_get_errors(&edn0, &unhealthy_fifos, &errors)); + CHECK_DIF_OK(dif_edn_get_recoverable_alerts(&edn0, &alerts)); + if (unhealthy_fifos != 0 || errors != 0 || alerts != 0) + LOG_ERROR("edn0: error=0x%x, unhealthy_fifos=0x%x, alerts=0x%x", errors, + unhealthy_fifos, alerts); + + CHECK_DIF_OK(dif_edn_get_errors(&edn1, &unhealthy_fifos, &errors)); + CHECK_DIF_OK(dif_edn_get_recoverable_alerts(&edn1, &alerts)); + if (unhealthy_fifos != 0 || errors != 0 || alerts != 0) + LOG_ERROR("edn1: error=0x%x, unhealthy_fifos=0x%x, alerts=0x%x", errors, + unhealthy_fifos, alerts); +} + +/** + * set edn auto mode + */ +void set_edn_auto_mode() { + const dif_edn_t edn0 = {.base_addr = + mmio_region_from_addr(TOP_MATCHA_EDN0_BASE_ADDR)}; + const dif_edn_t edn1 = {.base_addr = + mmio_region_from_addr(TOP_MATCHA_EDN1_BASE_ADDR)}; + + // entropy_testutils_stop_all(); + CHECK_DIF_OK(dif_edn_stop(&edn0)); + CHECK_DIF_OK(dif_edn_stop(&edn1)); + + // Re-enable EDN0 in auto mode. + const dif_edn_auto_params_t edn0_params = { + // EDN0 provides lower-quality entropy. Let one generate command return 8 + // blocks, and reseed every 32 generates. + .instantiate_cmd = + { + .cmd = 0x00000001 | // Reseed from entropy source only. + kMultiBitBool4False << 8, + .seed_material = + { + .len = 0, + }, + }, + .reseed_cmd = + { + .cmd = 0x00008002 | // One generate returns 8 blocks, reseed + // from entropy source only. + kMultiBitBool4False << 8, + .seed_material = + { + .len = 0, + }, + }, + .generate_cmd = + { + .cmd = 0x00008003, // One generate returns 8 blocks. + .seed_material = + { + .len = 0, + }, + }, + .reseed_interval = 32, // Reseed every 32 generates. + }; + CHECK_DIF_OK(dif_edn_set_auto_mode(&edn0, edn0_params)); + + // Re-enable EDN1 in auto mode. + const dif_edn_auto_params_t edn1_params = { + // EDN1 provides highest-quality entropy. Let one generate command + // return 1 block, and reseed after every generate. + .instantiate_cmd = + { + .cmd = 0x00000001 | // Reseed from entropy source only. + kMultiBitBool4False << 8, + .seed_material = + { + .len = 0, + }, + }, + .reseed_cmd = + { + .cmd = 0x00001002 | // One generate returns 1 block, reseed + // from entropy source only. + kMultiBitBool4False << 8, + .seed_material = + { + .len = 0, + }, + }, + .generate_cmd = + { + .cmd = 0x00001003, // One generate returns 1 block. + .seed_material = + { + .len = 0, + }, + }, + .reseed_interval = 4, // Reseed after every 4 generates. + }; + CHECK_DIF_OK(dif_edn_set_auto_mode(&edn1, edn1_params)); +} + +void ottf_external_isr(void) { + plic_isr_ctx_t plic_ctx = {.rv_plic = &plic, + .hart_id = kTopMatchaPlicTargetIbex0}; + + adc_ctrl_isr_ctx_t adc_ctrl_ctx = { + .adc_ctrl = &adc_ctrl, + .plic_adc_ctrl_start_irq_id = kTopMatchaPlicIrqIdAdcCtrlAonMatchDone, + .expected_irq = 0, + .is_only_irq = true}; + + top_matcha_plic_peripheral_t peripheral; + dif_adc_ctrl_irq_t adc_ctrl_irq; + isr_testutils_adc_ctrl_isr(plic_ctx, adc_ctrl_ctx, &peripheral, + &adc_ctrl_irq); + + CHECK(peripheral == kTopMatchaPlicPeripheralAdcCtrlAon); + CHECK(adc_ctrl_irq == kDifAdcCtrlIrqMatchDone); + interrupt_serviced = true; +} + +bool test_main() { + uint32_t event_idx; + int idx = 1; + int read_fifo_depth_val; + + bool deepsleep; + dif_pwrmgr_domain_config_t pwrmgr_config; + + const dif_entropy_src_config_t entropy_src_config = { + .fips_enable = true, + // Route the entropy data received from RNG to the FIFO. + .route_to_firmware = true, + .single_bit_mode = kDifEntropySrcSingleBitModeDisabled, + .health_test_threshold_scope = false, + .health_test_window_size = 0x0200, + .alert_threshold = 2, + }; + + init_units(); + + set_edn_auto_mode(); + CHECK_DIF_OK(dif_clkmgr_jitter_set_enabled(&clkmgr, kDifToggleEnabled)); + + // Enable both recoverable and fatal alerts + CHECK_DIF_OK(dif_alert_handler_configure_alert( + &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert, + kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled)); + CHECK_DIF_OK(dif_alert_handler_configure_alert( + &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert, + kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled)); + + LOG_INFO("1 test alert/rng after Deep sleep 1"); + pwrmgr_config = kDifPwrmgrDomainOptionUsbClockInActivePower; + ast_enter_sleep_states_and_check_functionality( + pwrmgr_config, entropy_src_config, kAlertVal7); + + LOG_INFO("2 test alert/rng after regular sleep (usb clk enabled)"); + LOG_INFO("force new adc conv set"); + pwrmgr_config = kDifPwrmgrDomainOptionUsbClockInActivePower | + kDifPwrmgrDomainOptionUsbClockInLowPower | + kDifPwrmgrDomainOptionMainPowerInLowPower; + ast_enter_sleep_states_and_check_functionality( + pwrmgr_config, entropy_src_config, kAlertVal8); + + LOG_INFO("3 test alert/rng after regular sleep (all clk disabled in lp)"); + LOG_INFO("force new adc conv set"); + pwrmgr_config = kDifPwrmgrDomainOptionMainPowerInLowPower | + kDifPwrmgrDomainOptionUsbClockInActivePower; + ast_enter_sleep_states_and_check_functionality( + pwrmgr_config, entropy_src_config, kAlertVal7); + + LOG_INFO("c code is finished"); + + return true; +}
diff --git a/sw/device/tests/sim_dv/spi_host_tx_rx_test.c b/sw/device/tests/sim_dv/spi_host_tx_rx_test.c new file mode 100644 index 0000000..b413cb3 --- /dev/null +++ b/sw/device/tests/sim_dv/spi_host_tx_rx_test.c
@@ -0,0 +1,268 @@ +// Copyright 2023 Google LLC. +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "sw/device/lib/arch/device.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_base.h" +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_spi_host.h" +#include "sw/device/lib/runtime/hart.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/pinmux_testutils.h" +#include "sw/device/lib/testing/rand_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/lib/testing/test_framework/status.h" + +OTTF_DEFINE_TEST_CONFIG(); + +/** + * Indicates the spi_host instance under test. + * + * This constant is backdoor_overwritten by the vseq. + * (See comment in uart_tx_rx_test.c for details on 'static volatile const' + * qualifiers) + */ +static volatile const uint8_t kSPIHostIdx = 0x0; + +#define DATA_SET_SIZE 16 + +static dif_spi_host_t spi_host; +static dif_pinmux_t pinmux; + +static const top_matcha_direct_pads_t spi_host0_direct_pads[6] = { + kTopMatchaDirectPadsSpiHost0Sck, // sck + kTopMatchaDirectPadsSpiHost0Csb, // csb + kTopMatchaDirectPadsSpiHost0Sd3, // sio[3] + kTopMatchaDirectPadsSpiHost0Sd2, // sio[2] + kTopMatchaDirectPadsSpiHost0Sd1, // sio[1] + kTopMatchaDirectPadsSpiHost0Sd0}; // sio[0] + +// pinmap defined in chip_if.sv (spi_device1_if) +static const top_matcha_muxed_pads_t spi_host1_muxed_pads[6] = { + kTopMatchaMuxedPadsIob0, // sck + kTopMatchaMuxedPadsIob1, // csb + kTopMatchaMuxedPadsIob6, // sio[3] + kTopMatchaMuxedPadsIob5, // sio[2] + kTopMatchaMuxedPadsIob4, // sio[1] + kTopMatchaMuxedPadsIob3, // sio[0] +}; + +// For spi_host1 +// sck output +// csb output +// sio[0:3] bidir (input+output) + +/** To setup the pinmux using the enum's in top_matcha.h ... + * + * - Choose corresponding pad/periph + * from... + * + * dif_result_t dif_pinmux_output_select(..., + * dif_pinmux_index_t mio_pad_output, | top_matcha_pinmux_mio_out_t + * dif_pinmux_index_t outsel) | top_matcha_pinmux_outsel_t + * dif_result_t dif_pinmux_input_select(..., + * dif_pinmux_index_t peripheral_input, | + * top_matcha_pinmux_peripheral_in_t dif_pinmux_index_t insel) | + * top_matcha_pinmux_insel_t + * + */ + +typedef struct pinmux_select { + dif_pinmux_index_t pad; + dif_pinmux_index_t peripheral; +} pinmux_select_t; + +static const pinmux_select_t pinmux_out_config[] = { + // spi_host1 + { + .pad = kTopMatchaPinmuxMioOutIob0, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sck, // SCK + }, + { + .pad = kTopMatchaPinmuxMioOutIob1, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Csb, // CSB + }, + { + .pad = kTopMatchaPinmuxMioOutIob3, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0, // sio[0] + }, + { + .pad = kTopMatchaPinmuxMioOutIob4, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1, // sio[1] + }, + { + .pad = kTopMatchaPinmuxMioOutIob5, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2, // sio[2] + }, + { + .pad = kTopMatchaPinmuxMioOutIob6, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3, // sio[3] + }, +}; + +static const pinmux_select_t pinmux_in_config[] = { + // spi_host1 + { + .pad = kTopMatchaPinmuxInselIob3, + .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd0, // sio[0] + }, + { + .pad = kTopMatchaPinmuxInselIob4, + .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd1, // sio[1] + }, + { + .pad = kTopMatchaPinmuxInselIob5, + .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd2, // sio[2] + }, + { + .pad = kTopMatchaPinmuxInselIob6, + .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd3, // sio[3] + }, +}; + +/** + * Initialize the provided SPI host. + */ +void init_spi_host(dif_spi_host_t *spi_host, + uint32_t peripheral_clock_freq_hz) { + dif_spi_host_config_t config = { + .spi_clock = peripheral_clock_freq_hz / 2, + .peripheral_clock_freq_hz = peripheral_clock_freq_hz, + .chip_select = {.idle = 2, .trail = 2, .lead = 2}, + .full_cycle = true, + .cpha = true, + .cpol = true, + }; + CHECK_DIF_OK(dif_spi_host_configure(spi_host, config)); + CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true)); +} + +/** + * Setup pads for spi_host0 + * + * This peripheral is 'direct' connected to the pads. + */ +void setup_pads_spi_host0(void) { + // set weak pull-ups for all the pads + dif_pinmux_pad_attr_t out_attr; + dif_pinmux_pad_attr_t in_attr = { + .slew_rate = 0, + .drive_strength = 0, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp}; + for (uint32_t i = 0; i <= ARRAYSIZE(spi_host0_direct_pads); ++i) { + CHECK_DIF_OK(dif_pinmux_pad_write_attrs(&pinmux, spi_host0_direct_pads[i], + kDifPinmuxPadKindDio, in_attr, + &out_attr)); + } +} + +/** + * Setup pads and pinmux for spi_host0 + * + * This peripheral is 'muxed', so configure the pinmux as well as pads. + */ +void setup_pinmux_pads_spi_host1(void) { + // Set weak pull-ups for the pads + dif_pinmux_pad_attr_t out_attr; + dif_pinmux_pad_attr_t in_attr = { + .slew_rate = 0, + .drive_strength = 0, + // set weak pull-ups for all the pads + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp}; + for (uint32_t i = 0; i <= ARRAYSIZE(spi_host1_muxed_pads); ++i) { + CHECK_DIF_OK(dif_pinmux_pad_write_attrs(&pinmux, spi_host1_muxed_pads[i], + kDifPinmuxPadKindMio, in_attr, + &out_attr)); + } + + // Setup Inputs + for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) { + pinmux_select_t setting = pinmux_in_config[i]; + CHECK_DIF_OK( + dif_pinmux_input_select(&pinmux, setting.peripheral, setting.pad)); + } + // Setup Outputs + for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) { + pinmux_select_t setting = pinmux_out_config[i]; + CHECK_DIF_OK( + dif_pinmux_output_select(&pinmux, setting.pad, setting.peripheral)); + } +} + +bool test_main(void) { + // Initialize the pinmux. + CHECK_DIF_OK(dif_pinmux_init( + mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux)); + pinmux_testutils_init(&pinmux); + + // Setup pinmux if required, enable weak pull-up on relevant pads + setup_pads_spi_host0(); // direct + setup_pinmux_pads_spi_host1(); // muxed + + // Setup spi host configuration + LOG_INFO("Testing spi_host%0d", kSPIHostIdx); + uintptr_t base_addr; + uint64_t clkHz; + switch (kSPIHostIdx) { + case 0: { + base_addr = TOP_MATCHA_SPI_HOST0_BASE_ADDR; + clkHz = kClockFreqPeripheralHz * 4; + break; + } + case 1: { + base_addr = TOP_MATCHA_SPI_HOST1_BASE_ADDR; + clkHz = kClockFreqPeripheralHz; + break; + } + default: + LOG_FATAL("Invalid kSPIHostIdx: %d", kSPIHostIdx); + } + CHECK_DIF_OK(dif_spi_host_init(mmio_region_from_addr(base_addr), &spi_host)); + init_spi_host(&spi_host, (uint32_t)clkHz); + + // DV sync message + LOG_INFO("spi host configuration complete"); + + uint32_t expected_data[DATA_SET_SIZE]; + uint32_t received_data[DATA_SET_SIZE]; + for (uint32_t i = 0; i < ARRAYSIZE(expected_data); ++i) { + expected_data[i] = rand_testutils_gen32(); + } + + // Define the segments: + // 1st segment, TX only, host sends out the first word. + // 2nd segment, Bidirectional. The external device begins sending back data + // that it received. So it always lags the TX by 1 word. + // 3rd segment, RX only, final word readback. + dif_spi_host_segment_t host_operations[3] = { + {.type = kDifSpiHostSegmentTypeTx, + .tx = {.width = kDifSpiHostWidthStandard, + .buf = &expected_data[0], + .length = 4}}, + + {.type = kDifSpiHostSegmentTypeBidirectional, + .bidir = {.width = kDifSpiHostWidthStandard, + .txbuf = &expected_data[1], + .rxbuf = received_data, + .length = (DATA_SET_SIZE - 1) * sizeof(uint32_t)}}, + + {.type = kDifSpiHostSegmentTypeRx, + .tx = {.width = kDifSpiHostWidthStandard, + .buf = &received_data[DATA_SET_SIZE - 1], + .length = 4}}, + }; + + // Issue the transaction + CHECK_DIF_OK(dif_spi_host_transaction(&spi_host, /*csid=*/0, host_operations, + ARRAYSIZE(host_operations))); + + CHECK_ARRAYS_EQ(received_data, expected_data, ARRAYSIZE(expected_data)); + + return true; +}
diff --git a/sw/device/tests/sim_dv/spi_passthrough_test.c b/sw/device/tests/sim_dv/spi_passthrough_test.c new file mode 100644 index 0000000..e8bb80c --- /dev/null +++ b/sw/device/tests/sim_dv/spi_passthrough_test.c
@@ -0,0 +1,460 @@ +// Copyright 2023 Google LLC. +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "sw/device/lib/arch/device.h" +#include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_rv_plic.h" +#include "sw/device/lib/dif/dif_spi_device.h" +#include "sw/device/lib/dif/dif_spi_host.h" +#include "sw/device/lib/runtime/hart.h" +#include "sw/device/lib/runtime/irq.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/pinmux_testutils.h" +#include "sw/device/lib/testing/spi_device_testutils.h" +#include "sw/device/lib/testing/spi_flash_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" + +OTTF_DEFINE_TEST_CONFIG(); + +// Bit map of command slots to be filtered. This is supplied by the DV +// environment. +const volatile uint32_t kFilteredCommands; + +// Whether to upload write commands and have software relay them. +const volatile uint8_t kUploadWriteCommands; + +static dif_pinmux_t pinmux; +static dif_rv_plic_t rv_plic; +static dif_spi_device_handle_t spi_device; +static dif_spi_host_t spi_host0; +static dif_spi_host_t spi_host1; + +// Enable pull-ups for spi_host data pins to avoid floating inputs. +static const pinmux_pad_attributes_t pinmux_pad_config[] = { + { + .pad = kTopMatchaMuxedPadsIob1, + .kind = kDifPinmuxPadKindMio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, + { + .pad = kTopMatchaMuxedPadsIob3, + .kind = kDifPinmuxPadKindMio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, + { + .pad = kTopMatchaDirectPadsSpiHost0Sd0, + .kind = kDifPinmuxPadKindDio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, + { + .pad = kTopMatchaDirectPadsSpiHost0Sd1, + .kind = kDifPinmuxPadKindDio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, + { + .pad = kTopMatchaDirectPadsSpiHost0Sd2, + .kind = kDifPinmuxPadKindDio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, + { + .pad = kTopMatchaDirectPadsSpiHost0Sd3, + .kind = kDifPinmuxPadKindDio, + .flags = kDifPinmuxPadAttrPullResistorEnable | + kDifPinmuxPadAttrPullResistorUp, + }, +}; + +/** + * A convenience struct to associate a mux selection that connects a pad and + * peripheral. This can be used for an input mux or an output mux. + */ +typedef struct pinmux_select { + dif_pinmux_index_t pad; + dif_pinmux_index_t peripheral; +} pinmux_select_t; + +static const pinmux_select_t pinmux_out_config[] = { + { + .pad = kTopMatchaPinmuxMioOutIob0, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Csb, + }, + { + .pad = kTopMatchaPinmuxMioOutIob2, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sck, + }, + { + .pad = kTopMatchaPinmuxMioOutIob1, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0, + }, + { + .pad = kTopMatchaPinmuxMioOutIob3, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1, + }, + // These peripheral I/Os are not assigned for tests. + // { + // .pad = ???, + // .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2, + // }, + // { + // .pad = ???, + // .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3, + // }, +}; + +static const pinmux_select_t pinmux_in_config[] = { + { + .pad = kTopMatchaPinmuxInselIob1, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0, + }, + { + .pad = kTopMatchaPinmuxInselIob3, + .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1, + }, + // These peripheral I/Os are not assigned for tests. + // { + // .pad = ???, + // .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2, + // }, + // { + // .pad = ???, + // .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3, + // }, +}; + +/** + * Initialize the provided SPI host. For the most part, the values provided are + * filler, as spi_host0 will be in passthrough mode and spi_host1 will remain + * unused throughout the test. + */ +void init_spi_host(dif_spi_host_t *spi_host, + uint32_t peripheral_clock_freq_hz) { + dif_spi_host_config_t config = { + .spi_clock = peripheral_clock_freq_hz / 2, + .peripheral_clock_freq_hz = peripheral_clock_freq_hz, + .chip_select = + { + .idle = 2, + .trail = 2, + .lead = 2, + }, + }; + CHECK_DIF_OK(dif_spi_host_configure(spi_host, config)); + CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true)); +} + +/** + * Handle an incoming Write Status command. + * + * Modifies the internal status register and relays the command out to the + * downstream SPI flash. + * + * @param status The aggregated value of all three flash status registers prior + * to this command's execution. + * @param offset The bit offset for the byte to be modified by the payload. + * @param opcode The opcode corresponding to the incoming command. + */ +void handle_write_status(uint32_t status, uint8_t offset, uint8_t opcode) { + uint8_t payload; + uint16_t occupancy; + uint32_t start_offset; + CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy( + &spi_device, &occupancy, &start_offset)); + CHECK(occupancy == 1); + CHECK_DIF_OK(dif_spi_device_read_flash_buffer( + &spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset, occupancy, + &payload)); + + status &= (0xffu << offset); + status |= (payload << offset); + CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spi_device, status)); + + spi_flash_testutils_issue_write_enable(&spi_host0); + + dif_spi_host_segment_t transaction[] = { + { + .type = kDifSpiHostSegmentTypeOpcode, + .opcode = opcode, + }, + { + .type = kDifSpiHostSegmentTypeTx, + .tx = + { + .width = kDifSpiHostWidthStandard, + .buf = &payload, + .length = 1, + }, + }, + }; + CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, + ARRAYSIZE(transaction))); + spi_flash_testutils_wait_until_not_busy(&spi_host0); + CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); +} + +/** + * Handle an incoming Chip Erase command. + * + * Relays the command out to the downstream SPI flash. + */ +void handle_chip_erase(void) { + spi_flash_testutils_erase_chip(&spi_host0); + CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); +} + +/** + * Handle an incoming Sector Erase command. + * + * Relays the command out to the downstream SPI flash. + */ +void handle_sector_erase(void) { + uint8_t occupancy; + CHECK_DIF_OK( + dif_spi_device_get_flash_address_fifo_occupancy(&spi_device, &occupancy)); + CHECK(occupancy == 1); + + uint32_t address; + CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address)); + + dif_toggle_t addr4b_enabled; + CHECK_DIF_OK( + dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled)); + + bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled); + spi_flash_testutils_erase_sector(&spi_host0, address, addr_is_4b); + CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); +} + +/** + * Handle an incoming Page Program command. + * + * Relays the command out to the downstream SPI flash. + */ +void handle_page_program(void) { + uint8_t address_occupancy; + CHECK_DIF_OK(dif_spi_device_get_flash_address_fifo_occupancy( + &spi_device, &address_occupancy)); + CHECK(address_occupancy == 1); + + uint32_t address; + CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address)); + + uint8_t payload[256]; + uint16_t payload_occupancy; + uint32_t start_offset; + CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy( + &spi_device, &payload_occupancy, &start_offset)); + CHECK(start_offset == 0); + CHECK(payload_occupancy <= sizeof(payload)); + CHECK_DIF_OK(dif_spi_device_read_flash_buffer( + &spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset, + payload_occupancy, payload)); + + dif_toggle_t addr4b_enabled; + CHECK_DIF_OK( + dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled)); + + bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled); + spi_flash_testutils_program_page(&spi_host0, payload, payload_occupancy, + address, addr_is_4b); + CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); +} + +// This function assumes only one command will ever be uploaded to the FIFO at a +// time. The IP does not currently make any such guarantee. +void spi_device_process_upload_fifo(void) { + dif_irq_type_t irq_type; + CHECK_DIF_OK(dif_spi_device_irq_get_type( + &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &irq_type)); + if (irq_type == kDifIrqTypeEvent) { + CHECK_DIF_OK(dif_spi_device_irq_acknowledge( + &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty)); + } + + // Uploaded commands should all set the busy bit, and WREN should have been + // submitted before the uploaded command. + uint32_t status; + CHECK_DIF_OK(dif_spi_device_get_flash_status_registers(&spi_device, &status)); + CHECK(status & kSpiFlashStatusBitWip); + CHECK(status & kSpiFlashStatusBitWel); + + uint8_t command; + CHECK_DIF_OK(dif_spi_device_pop_flash_command_fifo(&spi_device, &command)); + // Check command against the ones we expect. + // Call command-specific handlers, probably, which validate the commands. Then + // execute. + if (command == kSpiDeviceFlashOpWriteStatus1) { + handle_write_status(status, /*offset=*/0, command); + } else if (command == kSpiDeviceFlashOpWriteStatus2) { + handle_write_status(status, /*offset=*/8, command); + } else if (command == kSpiDeviceFlashOpWriteStatus3) { + handle_write_status(status, /*offset=*/16, command); + } else if (command == kSpiDeviceFlashOpChipErase) { + handle_chip_erase(); + } else if (command == kSpiDeviceFlashOpSectorErase) { + handle_sector_erase(); + } else if (command == kSpiDeviceFlashOpPageProgram) { + handle_page_program(); + } else { + CHECK(false, "Received unexpected command 0x%x", command); + } + + CHECK_DIF_OK(dif_spi_device_irq_set_enabled( + &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, + kDifToggleEnabled)); +} + +/** + * Check that the command FIFO is not empty, and mask the interrupt for deferred + * processing. + * + * Runs in interrupt context. + */ +void spi_device_isr(void) { + bool cmdfifo_not_empty; + CHECK_DIF_OK(dif_spi_device_irq_is_pending( + &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, + &cmdfifo_not_empty)); + CHECK(cmdfifo_not_empty); + CHECK_DIF_OK(dif_spi_device_irq_set_enabled( + &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, + kDifToggleDisabled)); +} + +/** + * Handle SPI device interrupts. + * + * Runs in interrupt context. + */ +void ottf_external_isr(void) { + const uint32_t kPlicTarget = kTopMatchaPlicTargetIbex0; + dif_rv_plic_irq_id_t plic_irq_id; + CHECK_DIF_OK(dif_rv_plic_irq_claim(&rv_plic, kPlicTarget, &plic_irq_id)); + + top_matcha_plic_peripheral_t peripheral = (top_matcha_plic_peripheral_t) + top_matcha_plic_interrupt_for_peripheral[plic_irq_id]; + + switch (peripheral) { + case kTopMatchaPlicPeripheralSpiDevice: + // Only the UploadCmdfifoNotEmpty interrupt is expected. + CHECK(plic_irq_id == kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty); + spi_device_isr(); + break; + default: + break; + } + + // Complete the IRQ at PLIC. + CHECK_DIF_OK(dif_rv_plic_irq_complete(&rv_plic, kPlicTarget, plic_irq_id)); +} + +bool test_main(void) { + // Initialize the pinmux. + CHECK_DIF_OK(dif_pinmux_init( + mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux)); + pinmux_testutils_init(&pinmux); + pinmux_testutils_configure_pads(&pinmux, pinmux_pad_config, + ARRAYSIZE(pinmux_pad_config)); + for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) { + pinmux_select_t setting = pinmux_in_config[i]; + CHECK_DIF_OK( + dif_pinmux_input_select(&pinmux, setting.peripheral, setting.pad)); + } + for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) { + pinmux_select_t setting = pinmux_out_config[i]; + CHECK_DIF_OK( + dif_pinmux_output_select(&pinmux, setting.pad, setting.peripheral)); + } + + // Initialize the PLIC. + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic)); + + // Initialize the spi_host devices. + CHECK_DIF_OK(dif_spi_host_init( + mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0)); + CHECK_DIF_OK(dif_spi_host_init( + mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host1)); + init_spi_host(&spi_host0, (uint32_t)kClockFreqPeripheralHz * 4); + init_spi_host(&spi_host1, (uint32_t)kClockFreqPeripheralHz); + + // Initialize spi_device. + mmio_region_t spi_device_base_addr = + mmio_region_from_addr(TOP_MATCHA_SPI_DEVICE_BASE_ADDR); + CHECK_DIF_OK(dif_spi_device_init_handle(spi_device_base_addr, &spi_device)); + bool upload_write_commands = (kUploadWriteCommands != 0); + spi_device_testutils_configure_passthrough(&spi_device, kFilteredCommands, + upload_write_commands); + + // Enable all spi_device and spi_host interrupts, and check that they do not + // trigger unless command upload is enabled. + dif_spi_device_irq_t all_spi_device_irqs[] = { + kDifSpiDeviceIrqGenericRxFull, kDifSpiDeviceIrqGenericRxWatermark, + kDifSpiDeviceIrqGenericTxWatermark, kDifSpiDeviceIrqGenericRxError, + kDifSpiDeviceIrqGenericRxOverflow, kDifSpiDeviceIrqGenericTxUnderflow, + kDifSpiDeviceIrqUploadCmdfifoNotEmpty, kDifSpiDeviceIrqReadbufWatermark, + kDifSpiDeviceIrqReadbufFlip, kDifSpiDeviceIrqTpmHeaderNotEmpty, + }; + for (int i = 0; i < ARRAYSIZE(all_spi_device_irqs); ++i) { + dif_spi_device_irq_t irq = all_spi_device_irqs[i]; + CHECK_DIF_OK(dif_spi_device_irq_set_enabled(&spi_device.dev, irq, + kDifToggleEnabled)); + } + CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqError, + kDifToggleEnabled)); + CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqSpiEvent, + kDifToggleEnabled)); + + dif_rv_plic_irq_id_t spi_irqs[] = { + kTopMatchaPlicIrqIdSpiDeviceGenericRxFull, + kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark, + kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark, + kTopMatchaPlicIrqIdSpiDeviceGenericRxError, + kTopMatchaPlicIrqIdSpiDeviceGenericRxOverflow, + kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow, + kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty, + kTopMatchaPlicIrqIdSpiDeviceReadbufWatermark, + kTopMatchaPlicIrqIdSpiDeviceReadbufFlip, + kTopMatchaPlicIrqIdSpiHost0Error, + kTopMatchaPlicIrqIdSpiHost0SpiEvent, + }; + for (int i = 0; i < ARRAYSIZE(spi_irqs); ++i) { + dif_rv_plic_irq_id_t irq = spi_irqs[i]; + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &rv_plic, irq, kTopMatchaPlicTargetIbex0, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, irq, 0x1)); + } + irq_external_ctrl(/*en=*/true); + + // Send the DV environment a specific message for synchronization. The + // sequencer can pick this up and know it can begin sending SPI flash + // transactions. + LOG_INFO("Test setup complete."); + + while (true) { + uint8_t spi_device_cmdfifo_occupancy; + irq_global_ctrl(/*en=*/false); + CHECK_DIF_OK(dif_spi_device_get_flash_command_fifo_occupancy( + &spi_device, &spi_device_cmdfifo_occupancy)); + if (spi_device_cmdfifo_occupancy == 0) { + wait_for_interrupt(); + } + irq_global_ctrl(/*en=*/true); + spi_device_process_upload_fifo(); + } + return true; +}