AN 947: Testing the Nios® II Software with Unit-Test Framework

ID 683606
Date 4/30/2021
Public

Intrinsic Function Overwrite

Nios® II software operates on top of the board support package (BSP) for the Nios® II processor. BSP provides many useful application programming interfaces (APIs) which assist software to execute on the Nios® II processor and interact with hardware. For Nios® II software, the hardware interaction means reading from or writing to certain registers in a certain way. Therefore, the System Mock can intercept these read or write (RW) operations to model the Nios® II software interactions with hardware.

In the Nios® II BSP, there are macros defined for register RW. The commonly used ones are IORD, IORD_32DIRECT, IOWR, and IOWR_32DIRECT. These macros call built-in functions __builtin_ldwio and __builtin_stwio. System Mock can provide new definitions for these built-in functions. Overwrite the definitions of these built-in functions to redirect the memory access and augment it with side effects.

One caveat of this technique is that developers must write code in a certain way. All Avalon® memory-mapped interface RW operations must use function calls (for example: IORD/IOWR). Memory access through pointers is allowed in this unit-test framework. The prerequisite is that the System Mock has allocated a memory block and replaced the base address in Nios® II code appropriately. However, System Mock cannot intercept or augment these pointer-based memory access.

Example code for intrinsic function overwrite:
// NIOS II software code // main.c alt_u32 mb_val = IORD_32DIRECT(U_MAILBOX_AVMM_BRIDGE_BASE, 0); // NIOS II BSP provides macros for generic read and write // HAL/inc/io.h #define __IO_CALC_ADDRESS_DYNAMIC(BASE, OFFSET) \ ((void *)(((alt_u8*)BASE) + (OFFSET))) #define IORD_32DIRECT(BASE, OFFSET) \ __builtin_ldwio (__IO_CALC_ADDRESS_DYNAMIC ((BASE), (OFFSET))) #define IOWR_32DIRECT(BASE, OFFSET, DATA) \ __builtin_stwio (__IO_CALC_ADDRESS_DYNAMIC ((BASE), (OFFSET)), (DATA)) // In unit-test, System Mock framework provides the definitions of these built-in functions // bsp_mock.h static alt_u32 __builtin_ldwio(void* src) { return SYSTEM_MOCK::get()->get_mem_word(src); } static void __builtin_stwio(void* dst, alt_u32 data) { return SYSTEM_MOCK::get()->set_mem_word(dst, data); } // System Mock goes through the list of IP Mock modules and passes on the Read/Write requests. // system_mock.cpp alt_u32 SYSTEM_MOCK::get_mem_word(void* addr) { alt_u32 ret = 0; // Dispatch to the appropriate handler based on the address for (auto& mock : m_memory_mocks) { if (mock->is_addr_in_range(addr)) { ret = mock->get_mem_word(addr); break; } } return ret; } void SYSTEM_MOCK::set_mem_word(void* addr, alt_u32 data) { // skipped ... } // In Mailbox IP mock module, define IP behavior in get/set_mem_word functions // mailbox_mock.h class MAILBOX_MOCK : public MEMORY_MOCK_IF { public: MAILBOX_MOCK(); virtual ~MAILBOX_MOCK(); void reset() override; alt_u32 get_mem_word(void* addr) override; void set_mem_word(void* addr, alt_u32 data) override; bool is_addr_in_range(void* addr) override; private: // A simple array to store register values in Mailbox std::array<alt_u32, U_MAILBOX_AVMM_BRIDGE_SPAN> m_mailbox_regs; };
Consider a simple FPGA design with a Nios® II system and a mailbox on an Avalon® memory-mapped interface. Here, a mailbox is a simple collection of hardware registers that allows Intel FPGA Nios® II processor to communicate with the outside world. These mailbox registers reside in 0x2000–0x2fff of the Avalon® memory-mapped interface address range. When the Nios® II software executes in an x86 unit-test environment, there are RW operations on this address range, which is arbitrary and probably illegal for CPU processes. To avoid that, in System Mock, user can define a 4KB array representing the mailbox. System Mock overwrites the intrinsic functions and redirects the memory access to this array.
Figure 1. Intrinsic Function Overwrite Example