What memory address are you accessing given the contents of registers? (computat
ID: 3651812 • Letter: W
Question
What memory address are you accessing given the contents of registers? (computation)My course is about "Computer Organization and Architecture" and we have been focussing on ARM.
Explanation / Answer
ARM recommends word alignment of peripheral registers even if they are 16-bit or 8-bit peripherals. In a little-endian system, the peripheral databus can connect directly to the least significant bits of the ARM databus and there is no need to multiplex (or duplicate) the peripheral databus onto high bits of the ARM databus. In a big-endian system, the peripheral databus can connect directly to the most significant bits of the ARM databus and there is no need to multiplex (or duplicate) the peripheral databus onto low bits of the ARM databus. ARM's AMBA APB bridge uses the above technique to simplify the bridge design. The result of this is that only word-aligned addresses should be used (whether byte, halfword or word tranfer), and a read will read garbage on any bits which are not connected to the peripheral. So, if a 32-bit word is read from a 16-bit peripheral, the top 16 bits of the register value must be cleared before use. For example, to access some 16-bit peripheral registers on 16-bit alignment, you might write: volatile unsigned short u16_IORegs[20]; This is fine providing your peripheral controller has the logic to route the peripheral databus to the high part (D31..D16) of the ARM databus as well as the low part (D15..D0) depending upon which address you are accessing. You should check if this multiplexing logic exists or not in your design (the standard ARM APB bridge does not support this). Alignment of registers If you wish to map 16-bit registers on 32-bit alignment as recommended, then you could use: volatile unsigned short u16_IORegs[40]; ... and only access even numbered registers - you will need to double the register number, for example, to access the fourth register you could use: x = u16_IORegs[8]; u16_IORegs[8] = newval; volatile unsigned int u32_IORegs[20]; ... where the registers are accessed as 32-bit full-width. But a simple peripheral controller such as ARM's AMBA APB bridge will read garbage into the top bits of the ARM register from the signals that are not connected to the peripheral (D31..D16 for a little-endian system). So, when such a peripheral is read, it must be cast to to an unsigned short to get the compiler to discard the upper 16 bits. For example, access reg 4 using: x = (unsigned short)u32_IORegs[4]; u32_IORegs[4] = newval; use a struct allows descriptive names to be used (more maintainable and legible) allows different register widths to be accomodated Note: padding should be made explict rather than relying on automatic padding added by the compiler, for example: struct PortRegs { unsigned short ctrlreg; /* offset 0 */ unsigned short dummy1; unsigned short datareg; /* offset 4 */ unsigned short dummy2; unsigned int data32reg; /* offset 8 */ } iospace; x = iospace.ctrlreg; iospace.ctrlreg = newval; Please note that peripheral locations should *not* be accessed using __packed structs (where unaligned members are allowed and there is no internal padding), or using C bitfields. This is because it is not possible to control the number and type of memory access that is being performed by the compiler. The result is code which is non-portable, has undesirable side-effects, and will not work as intended. The recommended way of accessing peripherals is through explicit use of architecturally-defined types such as int, short, char on their natural alignment. Mapping variables to specific addresses Memory mapped registers can be accessed from C in two ways: either by forcing a array or struct variable to a specific address, or by using a pointer to an array or struct (see below for details). Both generate efficient code - it is really down to a matter of personal preference. Forcing struct/array to a specific address The 'variable' should be declared it in a file on its own. When it is compiled, the object code for this file will only contain data. This data can be placed at a specified address using the ARM scatter-loading mechanism. This is the recommended method for placing all AREAs (code, data, etc) at required locations in the memory map. Create a C source file, for example, iovar.c which contains a declaration of the variable/array/struct, e.g. volatile unsigned short u16_IORegs[20]; or struct{ volatile unsigned reg1; volatile unsigned reg2; } mem_mapped_reg; Create a scatter-loading description file (called scatter.txt) containing the following: ALL 0x8000 { ALL 0x8000 { * (+RO,+RW,+ZI) } } IO 0x40000000 { IO 0x40000000 { iovar.o (+ZI) } } The scatter-loading description file must be specified at link time to the linker using the --scatter scatter.txt command line option. This creates two different load regions in your image: 'ALL' and 'IO'. The zero-initialised area from iovar.o (containing your array) goes into the IO area located at 0x40000000. All code (RO) and data areas (RW and ZI) from other object files go into the 'ALL' region which starts at 0x8000. If you have more than one group of variables (more than one set of memory mapped registers) you would need to define each group of variables as a separate execution region (though they could all lie within a single load region). To do this, each group of variables would need to be defined in a separate module. The benefit of using a scatter-loading description file is that all the (target-specific) absolute addresses chosen for your devices, code and data are located in one file, making maintenance easy. Furthermore, if you decide to change your memory map (for example, if peripherals are moved), you do not need to rebuild your entire project - you only need to re-link the existing objects. Alternatively, it is possible to use the #pragma arm section pragma to place the data into a specific section and then use scatter-loading to place that data at an explicit location. For further information, please see the ARM Compiler toolchains Compiler Reference documentation. Using a pointer to struct/array struct PortRegs { unsigned short ctrlreg; /* offset 0 */ unsigned short dummy1; unsigned short datareg; /* offset 4 */ unsigned short dummy2; unsigned int data32reg; /* offset 8 */ }; volatile struct PortRegs *iospace = (struct PortRegs *)0x40000000; x = iospace->ctrlreg; iospace->ctrlreg = newval; The pointer could be either local or global. If global, to avoid the base pointer being reloaded after function calls, make iospace a constant pointer to the struct by changing its definition to: volatile struct PortRegs * const iospace = (struct PortRegs *)0x40000000; Code efficiency The ARM compiler will normally use a 'base register' plus the immediate offset field available in the load/store instruction to compile struct member or specific array element access. In the ARM instruction set, LDR/STR word/byte instructions have a 4KB range, but LDRH/STRH instructions have a smaller immediate offset of 256 bytes. Equivalent 16-bit Thumb instructions are much more restricted - LDR/STR have a range of 32 words, LDRH/STRH have a range of 32 halfwords and LDRB/STRB have a range of 32 bytes. However, 32-bit Thumb instructions offer a significant improvement. Hence, it is important to group related peripheral registers near to each other if possible. The compiler will generally do a good job of minimising the number of instructions required to access the array elements or structure members by using base registers. Further information about the immediate offsets of LDR and STR instructions is available in the ARM Compiler toolchain - Assembler Reference documentation. There is a choice between one big C struct/array for the whole I/O space and smaller per-peripheral structs. In fact there isn't much difference in efficiency - the big struct might be a benefit if you are using ARM code where a base pointer can have a 4Kbyte range (for word/byte access) and the entire I/O space isRelated Questions
Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
drjack9650@gmail.com
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.