Getting Cortex-M1 running on an Artix-7 FPGA without a debugger can be challenging, but is certainly achievable with some effort. This article will walk through the key steps and considerations for accomplishing this goal.
Overview of Cortex-M1 and Artix-7
The Cortex-M1 processor is a 32-bit ARM processor intended for microcontroller applications. It has a simplified 3-stage pipeline compared to more complex Cortex-A series processors, making it suitable for constrained embedded systems. The Cortex-M1 lacks cache and has a single-cycle ALU, allowing for deterministic real-time behavior.
The Artix-7 FPGA from Xilinx is a mid-range FPGA family built on a 28nm process. It represents a lower cost, lower power option compared to Vivado series FPGAs. The Artix-7 contains programmable logic blocks, DSP slices, block RAM, and programmable I/O interfaces making it flexible for digital logic implementation.
Reasons for Running Cortex-M1 on Artix-7
There are several potential motivations for pairing a Cortex-M1 soft core processor with an Artix-7 FPGA:
- Prototyping an embedded system prior to ASIC implementation
- Leveraging FPGA flexibility for interfacing and pre-silicon software development
- Creating a customized processing subsystem tailored to application needs
- Cost-effective alternative to Cortex-M1 hard IP for low/medium volume productions
In particular, the ability to customize, add peripherals, and alter the system configuration provides unique advantages over a fixed Cortex-M1 SoC platform for embedded development.
Challenges of No External Debugger
Without an external debugger such as JTAG, there are several challenges that must be addressed:
- No run control to start/stop processor execution
- No visibility into register/memory contents
- No breakpoint support to pause execution at specific code locations
- No single-stepping through firmware code
- No runtime view of program flow, stalls, interrupts, etc
To work around these issues software-based approaches must be used:
- Bootloader with initialization code to configure system and start application
- Log print statements and memory dumps to output critical info
- Strategically located debug code that alters program flow
- Test routines to validate memory contents and peripheral state
- Timer-based interrupts to profile performance
Cortex-M1 Design Considerations on Artix-7
The following aspects should be considered when implementing a Cortex-M1 soft core on Artix-7 FPGA:
- Performance goals – The system clock speed and core configuration should be selected to meet performance targets. This guides the synthesis and implementation constraints.
- Memory architecture – Static or dynamic memory, caches, and bus width impact achievable clock speed. Dual-port SRAM blocks in Artix-7 can enable independent instruction and data accesses.
- Debug interface – A debug module is useful for visibility into the core even without JTAG. This could utilize FPGA GPIO or other internal interfaces.
- Peripherals – Needed peripherals like timers, UART, SPI, GPIO should be instantiated as part of the full Cortex-M1 subsystem design.
- Software stack – The C runtime, RTOS, drivers, and application software must be ported and optimized for the system.
Cortex-M1 Implementation Process
A general flow for implementing a Cortex-M1 soft core on Artix-7 without a debugger would comprise these steps:
- Create Vivado project, add IP integrator block design
- Customize and instantiate Cortex-M1 IP core
- Add memory interfaces and peripherals
- Connect debug module to FPGA GPIO or other internal interface
- Generate bitstream and export hardware definition
- Develop bootloader code to initialize system
- Port C runtime library and operating system
- Implement test applications and add debug/validation code
- Integrate with FPGA logic and I/O interfaces as needed
- Iteratively revise design based on validation to meet requirements
Key steps that help enable debug without JTAG include:
- Bootloader initialization to setup memory regions, clocks, peripherals
- Debug module connected to internal interface for debug print capability
- Strategic debugging code inserted in bootloader, libraries, and application
- Extensive validation via self-test routines to verify functionality
Bootloader Design Concepts
Since an external debugger is not available, a bootloader must perform critical system initialization such as:
- Configuring clock sources and PLLs
- Setting up external memory controller and SDRAM initialization
- Initializing on-chip memories and bus interfaces
- Enabling and configuring peripherals
- Loading application program code and data into memory
- Starting application processor execution
The bootloader will require a linker script to ensure it is located in internal memory at a fixed address after reset. Clocks and the debug module should be activated early so debug print statements can be enabled.
Self-diagnostic routines can be incorporated to validate memory interfaces, peripherals, and external memories. The bootloader can also manage firmware updates by erasing flash, loading new application images, and restarting.
Debug Module Integration
Connecting the Cortex-M1 debug module to an internal FPGA interface is essential for visibility. Potential options include:
- UART – Simple to output debug strings, but slow bandwidth
- AXI interface – Allows DMA or processor access to debug data
- GPIO – Quick to setup for status signals or very slow serial prints
- I2C – External I2C-based debugger could connect via this interface
- Ethernet – Network debugging greatly increases visibility
The debug module clock should be enabled early by the bootloader so it is available immediately. Care must be taken to ensure synchronization between clock domains if crossing between the processor and FPGA regions.
Firmware Debugging Techniques
In the absence of breakpoint debugging, firmware code can be made more debuggable utilizing methods like:
- Print statements – Simplest way to log info at key points
- Assertions – Validate assumptions and catch bugs closer to source
- Watchdog timers – Recover from stuck code paths
- Parameter checking – Detect invalid inputs to APIs
- Self-tests – Continuously verify operation during idle periods
- Statistics counters – Profile exact instruction counts, function calls, etc
- Execution tracing – Log time-stamped flow through code paths
Putting this instrumentation code directly into the source firmware gives increased observability without hardware debug capability.
Validating System Operation
Thorough validation of the Cortex-M1 subsystem on Artix-7 is essential to ensure correct operation without a debugger. Possible validation techniques include:
- Simulate critical components like bootloader using ARM models
- Generate bare-metal test programs that exercise all interfaces
- Iteratively validate each added peripheral during integration
- Build self-diagnostic routines to confirm memories and logic behavior
- Use FPGA hardware testbenches to validate interconnect
- Capture UART or bus trace data to observe system interactions
- Add CRC checking and sanity checks to critical data structures
- Confirm debug module outputs reflect program flow and processor state
By thoroughly testing the Cortex-M1 Artix-7 implementation pre-silicon, a stable platform can be validated for application deployment.
This covers the key considerations for pairing a Cortex-M1 processor with an Artix-7 FPGA without an external debugger. A well-designed bootloader provides initialization and debug print capabilities. Firmware code can be instrumentation to gain visibility in lieu of hardware debugging. And extensive validation at each stage of integration allows the system behavior to be progressively confirmed.
With careful attention to the software architecture, debug features, and validation methodology, Cortex-M1 on Artix-7 can enable an effective development platform without dependence on JTAG or other external debugging.