Tyrel Newton June 23, 2010
This is an upgraded (actually completely re-written) version of the original GCC/MicroBlaze port that is currently being distributed with FreeRTOS. It was developed using version 11.4 of the tools and has been thoroughly tested and optimized for use with a MicroBlaze on a Spartan-3A DSP 1800 with data and instruction caches to external DDR2. The intended usage model for this port is a preemptive scheduler. This means the port is not optimized for use as a non-preemptive scheduler and probably doesn't even support co-routines.
The port is thoroughly documented using Doxygen-style comments. Be sure to read portmacro.h and portdefs.h for information about this port. I have also included the FreeRTOSConfig.h file (slightly modified) that this port was developed in conjunction with.
The primary shortcoming of the original GCC/MicroBlaze port was that it did not "support" instruction and data caches. This re-written version of the port also better conforms to the ABI defined in the MicroBlaze reference guide. The original port was also written for version 7.x of the tools, which is currently way out-of-date.
Please keep all questions and comments about this port contained within this thread. I will try to provide support as needed, but no guarantees.
Tyrel
Hey Tyrel,
thanks for contributing the port! Works with no problems for me with a Microblaze on a Virtex 5, using both caches. I use the ISE 12.1 toolchain.
I just needed to add the configTOTAL_HEAP_SIZE parameters to FreeRTOSConfig.h. I think one can do this in the makefile too.
Just a question: I get the warning, that the port might be out of date because the XPAR_MICROBLAZE_D_OPB and XPAR_MICROBLAZE_I_OPB are not defined. That is because Xilinx removed the OPB support in 12.1. Do you expect any problems using your port then?
Thanks and regards!
Alex
Alex,
I'm glad to hear somebody else found it useful and that it works without problems.
The warnings about the OPB stuff will not cause any problems--as you noted, the OPB stuff was removed in 12.1. You can delete the offending lines of code if you want. In actuality, the warnings are completely correct--the port was developed for the 11.x series and now is out-of-date with respect to the 12.x series. But if it works for you, it means you've tested it under 12.1, thus saving me the trouble!
Regards,
Tyrel
Hi Tyrel,
I am currently investigating a softcore-rtos combination for an ethernet application.
I don't have any experiences with FreeRTOS. Does your port also include device drivers
for the XPS Eternet Lite Mac IP? Or are there other ethernet ip cores supported by FreeRTOS?
Best regards,
Jörg
Jorg,
I am currently using a combination of FreeRTOS, lwIP, and the Emaclite peripheral in an ethernet application. To make this work, I had to write (or adapt) three pieces of software:
1) the GCC/MicroBlaze port for FreeRTOS
2) the driver that bridges FreeRTOS with lwIP
3) the driver that bridges lwIP with the Emaclite
The port in this post only includes #1. There are plenty of examples of #2 in the various demos provided with FreeRTOS--I adapted one of these examples for my application. Xilinx provides an implementation of #3, though it needs to be tweaked slightly for use with the FreeRTOS/MicroBlaze port (mainly in how the driver sets up the interrupts).
Personally, I found the Xilinx implementation of #3 to be poor performing and poorly written in a couple of cases, but it does work.
Hope that helps get you started.
Regards,
Tyrel
Hi Tyrel,
Nice job on the new port, I've got it running on a small prototype project built on a Spartan-6. I'll be running a CAN interface on it, along with some other things hard-wired into the FPGA design.
One question I have is what are your thoughts on the 'proper' way to access the second timer module in the counter/timer device used for the OS tick?
The minor dilemma I see with it is that because xPortSetupTickTimer is called from inside vTaskStartScheduler, I can't configure or start the second timer before the scheduler starts without causing the call to XTmrCtr_Initialize to fail during scheduler initialization.
However, if I configure it later, say from within one of the tasks prior to it entering it's forever loop, then I need to halt the whole device (suspending the os tick timer in the process) while configuring the second timer. I'm a little loath to do this, as it seems a little kludgy to have application code muck with os resources like this.
Any ideas?
Regards,
Bill Howell
Bill,
Thanks, I'm glad to hear the port was useful.
Honestly, there isn't really a good answer to your question. FreeRTOS is structured in such a way that the tick interrupt must be provided by either application code or the porting layer--FreeRTOS is completely hardware independent. This is further complicated by the fact that the hardware surrounding a MicroBlaze is often quite varied. In my application, I'm no longer using a timer/counter module to generate the tick interrupt, but rather a custom peripheral that better meets my application's needs. To accommodate this, I had to restructure the port so that the tick interrupt could be configured and provided by the application layer more directly. The timer/counter module is not really an OS resource as far as FreeRTOS is concerned--its use in this port is more of an example than a requirement.
If you are truly loath to muck with the OS and/or porting layer, the simplest answer is to simply add a second timer/counter module and use it instead. Otherwise, I would suggest modifying the startup routines in the porting layer to better match your application's needs. These startup routines are far safer to muck with anyway than, say, the context-switching code written in assembly.
If I can find the time in the near future, I will try to post an updated version of the port which allows for more flexibility in how the tick interrupt is configured and generated.
Sorry the answer is not very specific, but I hope that helps some.
Tyrel
Not sure about this port, but some ports make the configuration of the tick interrupt dependent on the application, using a callback function (in a similar way to a tick hook function being defined by the application).
The port layer calls a function with prototype:
void vApplicationSetupTimerInterrupt( void );
which is defined in the application code. The application code can then use whichever timer it likes, but must also install the interrupt handler in the correct place within the vector table.
Regards.
Thank you both for the replies.
The Xilinx Timer/Counter IP is somewhat odd in that it provides 2 separate counter/timers that are interfaced with a single "device driver" from their codebase.
What I decided to do is to extend the original porthw.c and port.c interfaces to provide access to the second counter/timer as an 'alternate' counter timer, including checks during initialization to allow either one to be the source of setting up the main driver without causing problems when the other one ran. They can be initialized in either order that way.
I did this thinking that I only needed to use one timer in addition to the os tick timer for the software requirements, and doing so would minimize the number of IP blocks consumed to support the software portion of the overall design.
As it turned out, though, I did end up needing a total of three counter/timers, so the application counter/timers are in the second IP block, and I've rewritten the porthw.c and port.c files to use the first IP block timer pair for the OS tick and the runtime stats timer.
If you'd like a copy of my changes, let me know and I'll be glad to post them.
Bill,
Don't even get me started on the topic of Xilinx' device drivers...
As you say, the timer/counter IP contains two timers and one driver. The real purpose of having one driver should be to provide access to those functions that are shared between the timers, such as the simultaneous enable bit and the ability to use both timers to generate a single PWM output. However, the driver doesn't really provide access to these functions.
I have found that most of the time when I use a Xilinx IP block, I only use the "*_l.h" header file which essentially contains register access macros and the bit masks for the registers. The Xilinx drivers tend to be quite bloated. Though I will say I have relied quite heavily on the SPI and I2C drivers.
As far as your changes, feel free to post them if you like, that is what this thread is for.
I have been considering updating the port and packaging it up as a "Xilinx software service library"--where you specify the use of the OS in the "software platform settings" dialog. This would make integrating the port, and FreeRTOS, into new designs much easier. However, I'm wondering if there is any interest in this?
Tyrel
Hi Tyrel,
Yeah, I know what you mean about firmware designers idea of efficient coding styles.
It does look like the company I work for is going to be doing more microblaze designs, so I'll be around here for a while yet.
I've uploaded some changes to your port here, the majority of the changes is to reserve the use of the second module in the first counter/timer IP as the one to be used for profiling. I don't have a demo board, and the design I'm working with is restricted to the 64K BRAM internal to the Xilinx part, so I haven't been able to really test it out.
I moved the common initialization and configuration of the interrupt into a single function, and allow for either timer to get set up even if the other one is running.
As for integrating this as part of the Xilinx EDK, I'm pretty ambivalent - our process here separates firmware and software into different independent activities, so it's of little value to me - although I think there would be interest and value to others who do both the hardware and software designs themselves.
Regards,
Bill
Hi Tyrel,
thanks for posting the code, it is working very well on a Spartan6 Microblaze.
I have a question regarding the license that you released your code under: the FreeRTOS code is distributed under the GPL with a link exception, whereas your code seems to contain a pure GPL license (no link exception).
Was this your intent? Do you plan to change it to be in-line with the FreeRTOS license?
Thanks,
Chris
Chris,
Glad to hear it works. What version of the dev tools are you using? 11.x or 12.x.
Honestly, I did not pay much attention to the licensing when I posted the code. However, after reading through it, my intent is to match the licensing terms of FreeRTOS. I'm (very) slowly working on other updates and will include the link exception next time. I have no intention of preventing the use of the MicroBlaze port in closed-source commercial applications.
The only reasons I originally posted the code were to:
1) To get other test data points for free.
2) To gauge the interest in the use of FreeRTOS on the MicroBlaze platform.
3) To understand why other people choose FreeRTOS over one of the more supported options (i.e. xilkernal, Linux, VxWorks).
Regards,
Tyrel
Hey Tyrel,
> my intent is to match the licensing terms of FreeRTOS
that's awesome, thanks for the good work!
I've got the 12.3 tools set up under CentOS, and your code works flawlessly with it.
Regards,
Chris
Thank you so much! After one week trying and messing around with code (application code has grown larger than 1MB), this has been the solution to get Freertos on Microblaze running again. It just works perfect and should be integrated into the official branch.
There will be an official update of the existing code to the latest Xilinx tools released very soon.
Regards.
Hi Tyrel,
Firstly, thank you for the new port - it has been very useful! I can also confirm it works under Xilinx 13.1!
I was wondering if you could clarify a small part of your code as I'm a bit unclear as to what it's doing. In _interrupt _handler the "prologue" macro is used to create a 36 byte stack frame extending down from the mainline stack pointer, but I can't see where this is used afterwards. It isn't matched by an epilogue macro (from what I can see) and I don't understand why 36 bytes is needed. Perhaps I've been looking at it for too long but the only thing I can think of is that it's allocated for use by XIntc_DeviceInterruptHandler. Is this the case?
I know you must be busy and appreciate any help you can offer...
Kind Regards,
James.
James,
Excellent on the 13.1 compatibility. I'm about to upgrade to the 13.x series myself.
Regarding your question, I was confused for a bit because I'm using an updated version of the port that no longer does what you are specifically asking about. More specifically, the interrupt handler no longer uses the global stack frame, so no new 36 byte prologue is needed. I won't go into the perks of this as they're not relevant to your specific post. And as you say, I've been far too busy to clean up the latest version and post it to this thread.
The answer to your question though is you're correct. The local stack frame is for the call to XIntc_DeviceInterruptHandler, which takes an argument (namely the device ID). The MicroBlaze compiler (gcc) automatically saves passed arguments to the CALLER's stack frame. In this case, the _interrupt_handler assembly code is the CALLER and XIntc_DeviceInterruptHandler is the CALLEE. Therefore, there must be room in the _interrupt_handler's stack frame for the parameters it passes to XIntc_DeviceInterruptHandler. Said differently, local parameters for a function exist ABOVE the current function's stack frame (assuming a downward growing stack). 36 bytes (or 9 32-bit words) is the minimum frame size for a non-leaf function that does not use local variables. You can see this in a number of places: 1) the disassembly you can generate from the resulting ELF--look for the "addik r1, r1, -36" lines, 2) the size of the various structs I defined in portmacro.h, and 3) deciphering the MicroBlaze ABI defined in the reference manual. If you step through the debugger with #define portDEBUG_STACK_FRAMES set to 1, you can watch the contents of the global stack pointer variables (pxCallerFrame and pxTaskContext) change as you step through the _interrupt_handler and into the XIntc_DeviceInterruptHandler function. Keep in mind, the ABI in the reference manual is not 100% complete, the best place to see the ABI in action is by looking at the disassembly from compiled C code. I don't think the maintainers of the GCC port for the MicroBlaze are completely in-sync with the reference manual writers.
The reason there is no corresponding epilogue is because its simply not needed. The stack frame is completely re-created before returning from the interrupt handler to the mainline task. When another interrupt fires, the global stack frame used by the interrupt handler is simply reinitialized with the prologue in question.
Enough rambling.
Regards,
Tyrel
That's great - thanks Tyrel!
Kind Regards,
James.