Skip to content

Memory Mapped IO for 32-bit ARM #1834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Diltsman opened this issue Dec 15, 2018 · 15 comments
Closed

Memory Mapped IO for 32-bit ARM #1834

Diltsman opened this issue Dec 15, 2018 · 15 comments
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Milestone

Comments

@Diltsman
Copy link

Summary

In 32-bit ARM, peripheral control is achieved through control registers that are mapped into the processor's address space. These control registers must be read/written in 32-bit, 4-byte aligned chunks. Most control registers have multiple fields that can each be modified independently through a read, modify, write cycle.

Desired Features

  • Ability to specify each register as big-endian or little-endian. Most ARM processors have peripherals mapped via little-endian words, but I have encountered a processor that had some peripherals mapped as little-endian and other as big-endian. (Sounds like a lazy EE to me...)
  • Some way to guarantee that control registers are read/written one word (32-bits) at a time, with proper alignment.
  • Ability to read/write multiple fields of a control register at one time. This is usually an efficiency issue as multiple read, modify, write cycles don't usually cause problems. There are a few control registers where multiple fields must be modified during the same write, though.
  • Ability to specify that a control register is read-only, write-only, or read-write. Most peripherals have control registers that restrict if reads or writes are performed on them.

Optional Features

Bit-banding support. ARM Cortex-M processors have memory and control registers mapped to two different address ranges. The normal address range is accessed like normal memory. Reading a 32-bit, word-aligned location will load 32-bits into a register; writing a 32-bit, word-aligned location will write 32-bits from a register. The bit-banding address range maps each bit from the normal address range to a 32-bit, word-aligned location. The bit-banded word reads or writes a single bit from the normal address range as either 0x00000000 or 0x00000001. This is useful to avoid a read, modify, write when setting a 1-bit field in a control register.

Summary

Bit fields in Zig seem to be a well thought out feature, but they have some limitations that make it so that they cannot be used for memory-mapped IO on 32-bit ARM processors. All cases where bit fields cannot be used can be done with []u32 and bit masking, though this can be error prone.

Generally, Zig does a good job providing safe(ish) features to write low-level code, but bit fields cannot currently be used for memory mapped IO on 32-bit ARM.

@kristate
Copy link
Contributor

Thanks for the report! Do you have a repo that demonstrates your point?

@andrewrk andrewrk added this to the 0.4.0 milestone Dec 16, 2018
@Diltsman
Copy link
Author

Not yet. I am still working on blinking an LED. Once I get LED blinking and maybe DMA working, then I will have a repo.

@nic-donaldson
Copy link

This seems related to what I'm trying to get working. I want to write mmio code in this style for an armv6m board: https://gist.github.com/nic-donaldson/21abc0f0b9a83d3f4e96a5bbb55c9290

Problem is the generated code isn't great so I need to revert to the less clear (*volatile u32 ...).* = u32; approach. I compiled with command zig build-obj test.zig --static --target-arch armv6m --target-os freestanding --target-environ eabi --release-fast and version 0.3.0+82bf1eb7

This seems to fall under "Some way to guarantee that control registers are read/written one word (32-bits) at a time" and "Ability to read/write multiple fields of a control register at one time".

I'm trying to get an LED blinking too so I'll link that code if I get it working.

@andrewrk
Copy link
Member

Thanks for the writeup. I think we can make some improvements to the language based on this use case here.

Related to the endianness stuff: #649
This would solve the problem of using bitfields when some peripherals are little endian and some are big.

@ManDeJan
Copy link

Related to volatile semantics: #1664
I'd also like to add that this use case extends to other (embedded) architectures such as risc-v and avr

@justinbalexander
Copy link
Contributor

The problem appears to have gone away according to this godbolt link:

https://zig.godbolt.org/z/onwpqy

I don't see the multiple stores on either the 3.0 or trunk.

@kristate
Copy link
Contributor

kristate commented Jan 8, 2019

@vegecode I think this is because it was compiled under --release-fast.
Looking forward to the outcome of #978

@justinbalexander
Copy link
Contributor

Still only one store under debug at least as far as I can tell on my tiny phone screen.

@kristate
Copy link
Contributor

kristate commented Jan 8, 2019

@vegecode after closer review, it seems that @nic-donaldson was compiling for armv6m, which is reflected in the following disassembly: https://zig.godbolt.org/z/TONjH1

@kristate
Copy link
Contributor

kristate commented Jan 8, 2019

LLVM IR generated is the following:

  call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 inttoptr (i32 16 to i8*), i8* align 1 bitcast (%SIM_COPC_t* @0 to i8*), i32 4, i1 true), !dbg !112

@JinShil
Copy link

JinShil commented Jan 8, 2019

Allow me to offer some work I did on this in the D programming language to make memory-mapped IO for ARM microcontrollers more convenient and robust: https://github.com/JinShil/stm32f42_discovery_demo

The memory-mapped IO library can be found at https://github.com/JinShil/stm32f42_discovery_demo/blob/master/source/stm32f42/mmio.d and you can see its usage in the various peripheral libraries at https://github.com/JinShil/stm32f42_discovery_demo/tree/master/source/stm32f42

It has a number of features similar to those described by the OP. It achieves this using some unique D features such as compile-time execution, static if, and template metaprogramming.

I hope this will be helpful for whatever Zig decides to do. Good luck, and don't make me count bits 😉

@justinbalexander
Copy link
Contributor

I'm not an expert in IR. I see that the pointers are i8* though. Also the volatile flag is set.

Maybe this is relevant:
http://llvm.org/docs/LangRef.html#volatile

Would the memcpy produce different assembly is the pointers were i32 or the alignment were specified as being align 4? Maybe the hardcoded pointers should automatically get the highest alignment that they naturally qualify for?

@justinbalexander
Copy link
Contributor

justinbalexander commented Jan 8, 2019

Add align(@alignOf(u32)) to the intToPtr cast and the generated assembly is what the gentleman wants.

I tried to assign the alignment to the type instead and the compiler failed an assert:
zig/src/analyze.cpp:497

    const SIM_COPC_t align(@alignOf(u32)) = packed struct(u32) {
        _zeroes_31_3: u28,
        COPT: SIM_COPC_COPT,
        COPCLKS: SIM_COPC_COPCLKS,
        COPW: SIM_COPC_COPW
    };

With a hardcoded address, maybe the compiler should assign it the highest alignment possible if it is a pointer to a single item? If it is a pointer to an unknown number of items, maybe it should assign the alignment as the lowest of the addresses alignment or the items size.

So if there were a pointer to a packed struct(u16) with a hardcoded address of 0x10. The alignment of the address would be 16 but the type would have an alignment of 2 and so the pointer would get an alignment of two.

I see the docs have a lot of TODOs for the packed struct section. Maybe these should get fleshed out now that questions are being asked. I don't know what the intention is.

I don't think having to add the align attribute to the intToPtr cast is a huge burden. Mmio is always given in a header file from the vendor. If not, it's work that is only done once.

EDIT: alignment changed from 8 to 16. Woops.

@justinbalexander
Copy link
Contributor

Also, you could easily add the alignment using vim macros in just a few minutes for an entire file.

@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Apr 3, 2019
@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Sep 20, 2019
@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Dec 31, 2019
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 10, 2020
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 Nov 6, 2020
@SpexGuy SpexGuy added the use case Describes a real use case that is difficult or impossible, but does not propose a solution. label Mar 20, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 May 19, 2021
@ifreund
Copy link
Member

ifreund commented Sep 14, 2022

This should be well supported now that #5049 is implemented and packed structs use the same semantics as their backing integer type.

@ifreund ifreund closed this as completed Sep 14, 2022
@Vexu Vexu modified the milestones: 0.12.0, 0.10.0 Sep 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Projects
None yet
Development

No branches or pull requests

10 participants