diff --git a/systemc-components/backends/CMakeLists.txt b/systemc-components/backends/CMakeLists.txt index 02fa885b..e97e47c7 100644 --- a/systemc-components/backends/CMakeLists.txt +++ b/systemc-components/backends/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(char_backend_socket) add_subdirectory(char_backend_stdio) add_subdirectory(loop_back_backend) add_subdirectory(char_backend_file) +add_subdirectory(nor_flash_backend) if (NOT WIN32) add_subdirectory(legacy_char_backend_stdio) endif() diff --git a/systemc-components/backends/nor_flash_backend/CMakeLists.txt b/systemc-components/backends/nor_flash_backend/CMakeLists.txt new file mode 100644 index 00000000..9549d048 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/CMakeLists.txt @@ -0,0 +1 @@ +gs_create_dymod(nor_flash_backend) \ No newline at end of file diff --git a/systemc-components/backends/nor_flash_backend/README.md b/systemc-components/backends/nor_flash_backend/README.md new file mode 100644 index 00000000..22a9d311 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/README.md @@ -0,0 +1,87 @@ +# nor_flash_backend + +Native SystemC/TLM NOR flash backend for the QSPI controller. + +## Architecture + +``` +┌─────────────────────┐ biflow_socket ┌──────────────────────┐ +│ QSPI module │ ◄──────────────────────────► │ nor_flash_backend │ +│ (quic::qspi) │ │ │ +│ │ last_write_fragment_signal │ m_binary_content[] │ +│ │ ─────────────────────────── ►│ m_sfdp_data[] │ +└─────────────────────┘ │ m_device_id[] │ + └──────────────────────┘ +``` + +The QSPI sends commands to the backend via `b_transport()`. The backend +processes them and enqueues response bytes back via `biflow_socket.enqueue()`. + +The `last_write_fragment_signal` is driven by the QSPI to indicate whether +the current DMA write fragment is the last one in the chain. The backend uses +this to clear WEL (Write Enable Latch) only after the final fragment, matching +real NOR flash behaviour for multi-fragment DMA writes. + +## Command set + +| Opcode | Command | Behaviour | +|--------|---------|-----------| +| `0x03` | READ | Read from `m_binary_content` | +| `0x13` | READ4 | Read (4-byte address mode) | +| `0x0C` | FAST_READ4 | Fast read (4-byte address) | +| `0x6C` | OUTPUT_FAST_READ4 | Quad output fast read | +| `0xEC` | QIOR4 | Quad I/O read | +| `0x12` | PP4 | Page program (AND write into `m_binary_content`) | +| `0x34` | PP4_QUAD | Quad page program | +| `0x21` | ERASE_4K | Erase 4 KB sector (fill with `0xFF`) | +| `0x20` | ERASE_4K (3B) | Erase 4 KB sector | +| `0x52` | ERASE_32K | Erase 32 KB sector | +| `0xD8` | ERASE_SECTOR | Erase 64 KB sector | +| `0xDC` | ERASE4_SECTOR | Erase 64 KB sector (4-byte address) | +| `0xC7/0x60` | BULK_ERASE | Erase entire flash | +| `0x06` | WRITE_ENABLE | Set WEL in status register | +| `0x04` | WRITE_DISABLE | Clear WEL | +| `0x05` | READ_STATUS | Return status register | +| `0x70` | READ_FLAG_STATUS | Return flag status register | +| `0x65` | READ_ENHANCED_VOL_CFG | Return enhanced volatile config | +| `0x85` | READ_VOLATILE_CFG | Return volatile config | +| `0x9F` | READ_ID | Return device ID bytes | +| `0x5A` | READ_SFDP | Return SFDP table bytes | +| `0x01` | WRITE_STATUS | Write status register | +| `0x61` | WRITE_ENHANCED_VOL_CFG | Write enhanced volatile config | +| `0x81` | WRITE_VOLATILE_CFG | Write volatile config | +| `0xB7` | ENTER_4B_ADDR | No-op (acknowledged) | +| `0xB9/0xAB` | ENTER/EXIT_DPD | No-op (acknowledged) | +| `0x66/0x99` | RESET_ENABLE/RESET | No-op (acknowledged) | +| `0x50` | CLEAR_FLAG_STATUS | Reset flag status register to `FSR_READY` | + +### Write semantics + +NOR flash is write-once per bit: `PP4` performs `memory[addr] &= data` (bits +can only be cleared, not set). Erase restores bits to `0xFF`. + +WEL is cleared automatically after each write or erase operation. For DMA +multi-fragment writes, WEL is only cleared after the **last** fragment +(signalled by `last_write_fragment_signal = true`). + +## Lua configuration + +Configured via `nor_flash_backend_func()` in `qup.lua`: + +```lua +nor_flash_backend_func({ + binary_path = "/path/to/singleimage.bin", + sfdp_data = "0x53,0x46,0x44,0x50,...", -- SFDP table bytes (hex, comma-separated) + device_id = "0x20,0xBB,0x19,0x00", -- JEDEC device ID +}) +``` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `binary_path` | `""` | Path to the NOR flash binary image | +| `sfdp_data` | `""` | SFDP table content as comma-separated hex bytes | +| `device_id` | `"0x20,0xBB,0x19,0x00"` | JEDEC READ_ID response bytes | + +The binary image is loaded once at `end_of_elaboration()` and kept in a +`std::vector`. Writes and erases modify this in-memory buffer; the +file on disk is not modified. diff --git a/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h b/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h new file mode 100644 index 00000000..122f0e8d --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef NOR_FLASH_BACKEND_H +#define NOR_FLASH_BACKEND_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief SystemC NOR Flash Backend for QSPI + * + * CCI Parameters: + * - binary_path: Path to the binary image file + * - sfdp_data: Comma-separated hex string of SFDP table bytes + * - device_id: Comma-separated hex string of device ID bytes + */ +class nor_flash_backend : public sc_core::sc_module +{ +public: + SCP_LOGGER(); + gs::biflow_socket biflow_socket; + TargetSignalSocket last_write_fragment_signal; + + cci::cci_param p_binary_path; + cci::cci_param p_sfdp_data; + cci::cci_param p_device_id; + + nor_flash_backend(sc_core::sc_module_name name); + + void end_of_elaboration() override; + void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay); + +private: + // Flash command opcodes + enum FlashOpcode : uint8_t { + RESET_ENABLE_CMD = 0x66, + RESET_CMD = 0x99, + READ_STATUS_CMD = 0x05, + READ_STATUS_2_CMD = 0x3F, + WRITE_STATUS_2_CMD = 0x3E, + WRITE_ENABLE_CMD = 0x06, + WRITE_DISABLE_CMD = 0x04, + READ_ID_CMD = 0x9F, + READ_SFDP_CMD = 0x5A, + READ_CFG1_CMD = 0x35, + READ_FLAG_STATUS_CMD = 0x70, + READ_SECURITY_CMD = 0x2B, + WRITE_STATUS_CMD = 0x01, + ENTER_4B_ADDR_CMD = 0xB7, + READ_CFG_REG_CMD = 0x15, + READ_ENHANCED_VOL_CFG_CMD = 0x65, + ENTER_DPD = 0xB9, + EXIT_DPD = 0xAB, + WRITE_ENHANCED_VOL_CFG_CMD = 0x61, + WRITE_OCTAL_EN_STATUS_2_CMD = 0x31, + CLEAR_OCTAL_EN_STATUS_2_CMD = 0x3E, + ENABLE_8S_8S_8S_MODE_SEQ = 0xE8, + CLEAR_ERR_REGS = 0xB6, + CLEAR_FLAG_STATUS_REG = 0x50, + WRITE_CFG2_CMD = 0x72, + READ_VOLATILE_CFG_CMD = 0x85, + WRITE_VOLATILE_CFG_CMD = 0x81, + READ_CMD = 0x03, + READ4_CMD = 0x13, + FAST_READ4_CMD = 0x0C, + OUTPUT_FAST_READ4_CMD = 0x6C, + QIOR4_CMD = 0xEC, + ERASE4_SECTOR_CMD = 0xDC, + ERASE4_4K_CMD = 0x21, + ERASE_SECTOR_CMD = 0xD8, + ERASE_4K_CMD = 0x20, + ERASE_32K_CMD = 0x52, + BULK_ERASE_CMD = 0xC7, + BULK_ERASE_CMD2 = 0x60, + PP4_CMD = 0x12, + PP4_QUAD_CMD = 0x34 + }; + + std::vector m_binary_content; + std::vector m_sfdp_data; + std::vector m_device_id; + + uint8_t m_status_reg; ///< SR1 [7]=SRWD [6:3]=BP3:0 [1]=WEL [0]=WIP + uint8_t m_flag_status_reg; ///< FSR [7]=READY [6]=ERASE_SUSP [5]=ERASE_ERR [4]=PROG_ERR [3]=VPP_ERR [2]=PROG_SUSP + ///< [1]=PROT_ERR [0]=ADDR_MODE + uint8_t m_volatile_cfg; ///< VCR [7:4]=DUMMY_CYCLES [3]=XIP_DIS [2:1]=rsvd [0]=WRAP_DIS (default 0xFB) + uint8_t m_enhanced_vol_cfg; ///< EVCR [7]=DRV_STR [6]=HOLD_DIS [5]=QUAD_DIS [4]=DUAL_DIS [3:0]=rsvd (default 0xFF = + ///< SPI mode) + bool m_last_write_fragment = true; ///< true = current PP4 is the last DMA fragment → clear WEL after write + + static constexpr uint8_t SR_WEL = 0x02; ///< Status reg: Write Enable Latch (bit 1) + static constexpr uint8_t FSR_READY = 0x80; ///< Flag status: operation complete (bit 7) + static constexpr uint8_t FSR_ERASE_ERR = 0x20; ///< Flag status: erase error (bit 5) + static constexpr uint8_t FSR_PROG_ERR = 0x10; ///< Flag status: program error (bit 4) + static constexpr uint32_t SECTOR_SIZE_4K = 0x1000; ///< 4 KB subsector + static constexpr uint32_t SECTOR_SIZE_32K = 0x8000; ///< 32 KB subsector + static constexpr uint32_t SECTOR_SIZE_64K = 0x10000; ///< 64 KB sector + + bool load_binary(const std::string& path); + std::vector parse_hex_string(const std::string& hex_str); + void send_data_to_qspi(const uint8_t* data, uint32_t length); + void send_single_byte(uint8_t value); + + void handle_read_sfdp(uint64_t address, uint32_t tx_len); + void handle_read_id(uint32_t tx_len); + void handle_read_status(); + void handle_read_flag_status(); + void handle_read_enhanced_vol_cfg(); + void handle_read_volatile_cfg(); + void handle_read_memory(uint64_t address, uint32_t tx_len, uint8_t* out_buf); + void handle_write_memory(uint64_t address, uint8_t* data_ptr, uint32_t tx_len); +}; + +extern "C" void module_register(); + +#endif // NOR_FLASH_BACKEND_H \ No newline at end of file diff --git a/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc b/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc new file mode 100644 index 00000000..252fcf85 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "nor_flash_backend.h" + +nor_flash_backend::nor_flash_backend(sc_core::sc_module_name name) + : sc_core::sc_module(name) + , biflow_socket("biflow_socket") + , last_write_fragment_signal("last_write_fragment_signal") + , p_binary_path("binary_path", "", "Path to the NOR flash binary image file") + , p_sfdp_data("sfdp_data", "", "Comma-separated hex string of SFDP table bytes") + , p_device_id("device_id", "0x20,0xBB,0x19,0x00", "Comma-separated hex string of device ID bytes") + , m_status_reg(0x00) + , m_flag_status_reg(0x80) + , m_volatile_cfg(0xFB) + , m_enhanced_vol_cfg(0xFF) +{ + biflow_socket.register_b_transport(this, &nor_flash_backend::b_transport); + last_write_fragment_signal.register_value_changed_cb([&](bool value) { m_last_write_fragment = value; }); +} + +void nor_flash_backend::end_of_elaboration() +{ + std::string bin_path = p_binary_path.get_value(); + if (!bin_path.empty()) { + if (!load_binary(bin_path)) { + SCP_ERR(()) << "Failed to load binary: " << bin_path.c_str(); + } else { + SCP_INFO(()) << "Loaded binary '" << bin_path << "' (" << m_binary_content.size() << " bytes)"; + } + } else { + SCP_WARN(()) << "No binary_path configured"; + } + + m_sfdp_data = parse_hex_string(p_sfdp_data.get_value()); + m_device_id = parse_hex_string(p_device_id.get_value()); + + biflow_socket.can_receive_any(); +} + +bool nor_flash_backend::load_binary(const std::string& path) +{ + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) return false; + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + m_binary_content.resize(static_cast(size)); + return static_cast(file.read(reinterpret_cast(m_binary_content.data()), size)); +} + +std::vector nor_flash_backend::parse_hex_string(const std::string& hex_str) +{ + std::vector result; + std::stringstream ss(hex_str); + std::string token; + while (std::getline(ss, token, ',')) { + size_t start = token.find_first_not_of(" \t"); + if (start == std::string::npos) continue; + token = token.substr(start); + size_t end = token.find_last_not_of(" \t"); + if (end != std::string::npos) token = token.substr(0, end + 1); + unsigned long val = std::strtoul(token.c_str(), nullptr, 0); + result.push_back(static_cast(val)); + } + return result; +} + +void nor_flash_backend::send_data_to_qspi(const uint8_t* data, uint32_t length) +{ + /* + * Use enqueue() to send data back to the QSPI controller. + * We must NOT use force_send() here because b_transport is called from + * within the QSPI module's force_send() context — calling force_send() + * back would cause re-entrancy into the QSPI module's receive() method. + * enqueue() queues data for flow-controlled delivery, avoiding this issue. + */ + SCP_DEBUG(()) << "send_data_to_qspi: enqueueing " << length << " bytes"; + for (uint32_t i = 0; i < length; i++) { + biflow_socket.enqueue(data[i]); + } +} + +void nor_flash_backend::send_single_byte(uint8_t value) { send_data_to_qspi(&value, 1); } + +void nor_flash_backend::b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) +{ + uint8_t opcode = static_cast(trans.get_command()); + uint64_t address = trans.get_address(); + uint32_t tx_len = trans.get_data_length(); + uint8_t* data_ptr = trans.get_data_ptr(); + + SCP_DEBUG(()) << "b_transport: opcode=0x" << std::hex << static_cast(opcode) << " addr=0x" << address + << " len=" << std::dec << tx_len; + + switch (opcode) { + // ---- READ commands that return data ---- + case READ_SFDP_CMD: + SCP_DEBUG(()) << "READ_SFDP_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_sfdp(address, tx_len); + break; + case READ_ID_CMD: + SCP_DEBUG(()) << "READ_ID_CMD len=" << tx_len; + handle_read_id(tx_len); + break; + case READ_STATUS_CMD: + SCP_DEBUG(()) << "READ_STATUS_CMD status_reg=0x" << std::hex << static_cast(m_status_reg); + handle_read_status(); + break; + case READ_FLAG_STATUS_CMD: + SCP_DEBUG(()) << "READ_FLAG_STATUS_CMD flag=0x" << std::hex << static_cast(m_flag_status_reg); + handle_read_flag_status(); + break; + case READ_ENHANCED_VOL_CFG_CMD: + SCP_DEBUG(()) << "READ_ENHANCED_VOL_CFG_CMD val=0x" << std::hex << static_cast(m_enhanced_vol_cfg); + handle_read_enhanced_vol_cfg(); + break; + case READ_VOLATILE_CFG_CMD: + SCP_DEBUG(()) << "READ_VOLATILE_CFG_CMD val=0x" << std::hex << static_cast(m_volatile_cfg); + handle_read_volatile_cfg(); + break; + case READ_CMD: + SCP_DEBUG(()) << "READ_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, nullptr); + break; + + // ---- DMA read commands ---- + case READ4_CMD: + case FAST_READ4_CMD: + SCP_DEBUG(()) << "READ4/FAST_READ4 addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, data_ptr); + break; + + case OUTPUT_FAST_READ4_CMD: + case QIOR4_CMD: + SCP_DEBUG(()) << "OUTPUT_FAST_READ4/QIOR4 addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, data_ptr); + break; + + // ---- WRITE commands ---- + case WRITE_ENABLE_CMD: + SCP_DEBUG(()) << "WRITE_ENABLE_CMD"; + m_status_reg |= 0x02; // set WEL (bit 1) + break; + case WRITE_DISABLE_CMD: + SCP_DEBUG(()) << "WRITE_DISABLE_CMD"; + m_status_reg &= ~0x02; // clear WEL (bit 1) + break; + case WRITE_ENHANCED_VOL_CFG_CMD: + SCP_DEBUG(()) << "WRITE_ENHANCED_VOL_CFG_CMD val=0x" << std::hex + << (data_ptr ? static_cast(data_ptr[0]) : -1); + if (data_ptr) m_enhanced_vol_cfg = data_ptr[0]; + break; + case WRITE_VOLATILE_CFG_CMD: + SCP_DEBUG(()) << "WRITE_VOLATILE_CFG_CMD val=0x" << std::hex << (data_ptr ? static_cast(data_ptr[0]) : -1); + if (data_ptr) m_volatile_cfg = data_ptr[0]; + break; + case PP4_CMD: + case PP4_QUAD_CMD: + SCP_DEBUG(()) << "PP4_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_write_memory(address, data_ptr, tx_len); + break; + + case WRITE_STATUS_CMD: + if (!data_ptr || !(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "WRITE_STATUS: WEL=0, ignoring"; + break; + } + m_status_reg = data_ptr[0] & ~(SR_WEL | 0x01); // WEL and WIP are read-only + m_status_reg &= ~SR_WEL; // HW clears WEL after operation + break; + + // ---- No-op commands (acknowledged but no action needed) ---- + case ENTER_4B_ADDR_CMD: + case RESET_ENABLE_CMD: + case RESET_CMD: + case ENTER_DPD: + case EXIT_DPD: + case READ_STATUS_2_CMD: + case READ_CFG1_CMD: + case READ_CFG_REG_CMD: + case WRITE_CFG2_CMD: + case READ_SECURITY_CMD: + case WRITE_OCTAL_EN_STATUS_2_CMD: + case ENABLE_8S_8S_8S_MODE_SEQ: + case CLEAR_ERR_REGS: + case CLEAR_FLAG_STATUS_REG: + SCP_DEBUG(()) << "CLEAR_FLAG_STATUS_REG"; + m_flag_status_reg = FSR_READY; + break; + + // ---- Erase commands ---- + case ERASE4_4K_CMD: + case ERASE4_SECTOR_CMD: + case ERASE_4K_CMD: + case ERASE_32K_CMD: + case ERASE_SECTOR_CMD: { + uint32_t erase_size; + if (opcode == ERASE4_4K_CMD || opcode == ERASE_4K_CMD) + erase_size = SECTOR_SIZE_4K; + else if (opcode == ERASE_32K_CMD) + erase_size = SECTOR_SIZE_32K; + else + erase_size = SECTOR_SIZE_64K; + SCP_DEBUG(()) << "ERASE addr=0x" << std::hex << address << " size=" << erase_size; + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Erase: WEL=0, setting ERASE_ERR"; + m_flag_status_reg |= FSR_ERASE_ERR; + break; + } + if (!m_binary_content.empty() && address < m_binary_content.size()) { + uint32_t erase_end = std::min(static_cast(address + erase_size), + static_cast(m_binary_content.size())); + memset(&m_binary_content[address], 0xFF, erase_end - address); + } + m_status_reg &= ~SR_WEL; + break; + } + case BULK_ERASE_CMD: + case BULK_ERASE_CMD2: + SCP_DEBUG(()) << "BULK_ERASE"; + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Bulk erase: WEL=0, setting ERASE_ERR"; + m_flag_status_reg |= FSR_ERASE_ERR; + break; + } + memset(m_binary_content.data(), 0xFF, m_binary_content.size()); + m_status_reg &= ~SR_WEL; + break; + + default: + SCP_WARN(()) << "Unknown opcode 0x" << std::hex << static_cast(opcode); + break; + } + trans.set_response_status(tlm::TLM_OK_RESPONSE); +} + +void nor_flash_backend::handle_read_sfdp(uint64_t address, uint32_t tx_len) +{ + if (m_sfdp_data.empty()) { + SCP_WARN(()) << "SFDP data not configured"; + return; + } + if (address >= m_sfdp_data.size() || address + tx_len > m_sfdp_data.size()) { + SCP_WARN(()) << "SFDP read out of range: addr=0x" << std::hex << address << " len=" << std::dec << tx_len + << " sfdp_size=" << m_sfdp_data.size(); + return; + } + send_data_to_qspi(&m_sfdp_data[address], tx_len); +} + +void nor_flash_backend::handle_read_id(uint32_t tx_len) +{ + uint32_t n = std::min(tx_len, static_cast(m_device_id.size())); + send_data_to_qspi(m_device_id.data(), n); +} + +void nor_flash_backend::handle_read_status() { send_single_byte(m_status_reg); } + +void nor_flash_backend::handle_read_flag_status() { send_single_byte(m_flag_status_reg); } + +void nor_flash_backend::handle_read_enhanced_vol_cfg() { send_single_byte(m_enhanced_vol_cfg); } + +void nor_flash_backend::handle_read_volatile_cfg() { send_single_byte(m_volatile_cfg); } + +void nor_flash_backend::handle_read_memory(uint64_t address, uint32_t tx_len, uint8_t* out_buf) +{ + if (m_binary_content.empty() || address + tx_len > m_binary_content.size()) { + SCP_WARN(()) << "read out of range: addr=0x" << std::hex << address << " len=" << std::dec << tx_len + << ", returning 0xFF"; + if (out_buf) { + memset(out_buf, 0xFF, tx_len); + } else { + std::vector ff_data(tx_len, 0xFF); + send_data_to_qspi(ff_data.data(), tx_len); + } + return; + } + SCP_DEBUG(()) << "read memory: addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + if (out_buf) { + memcpy(out_buf, &m_binary_content[address], tx_len); + } else { + send_data_to_qspi(&m_binary_content[address], tx_len); + } +} + +void nor_flash_backend::handle_write_memory(uint64_t address, uint8_t* data_ptr, uint32_t tx_len) +{ + if (!data_ptr || m_binary_content.empty()) { + SCP_WARN(()) << "Write: no data or no binary loaded"; + return; + } + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Write: WEL=0, setting PROG_ERR"; + m_flag_status_reg |= FSR_PROG_ERR; + return; + } + if (address + tx_len > m_binary_content.size()) { + SCP_DEBUG(()) << "Write: expanding binary to " << std::hex << (address + tx_len); + m_binary_content.resize(static_cast(address + tx_len), 0xFF); + } + for (uint32_t i = 0; i < tx_len; i++) { + m_binary_content[address + i] &= data_ptr[i]; + } + if (m_last_write_fragment) { + m_status_reg &= ~SR_WEL; + } +} + +void module_register() { GSC_MODULE_REGISTER_C(nor_flash_backend); }