Skip to content

Peripheral Access Conventions

bunnie edited this page Oct 11, 2020 · 12 revisions

Rationale/Requirements

  • Make it clear what's going on
  • Make it easy to inspect constants for correctness
  • Base off of "Volatile" crate syntax
  • Rely on Rust optimizer to turn otherwise complicated ops into the desired shift-and-masks
  • Split into "base/offset", so the crate can be used for e.g. virtual memory mapped peripherals

Basic strategy

To help things out, the following constants are generated:

  • {PERIPHERAL}_BASE -- CSR-space physical base address of peripheral
  • {PERIPHERAL}_CSR_LEN -- length of CSR window
  • {PERIPHERAL}_MEM -- wishbone memory region associated with it
  • {PERIPHERAL}_MEM_LEN -- length of wishbone window
  • {PERIPHERAL}_{REGISTER} -- offset of register in peripheral block
  • {PERIPHERAL}_{REGISTER}_{FIELD} -- sub-field of register, has format [u32; 2] = [mask, offset]

For the register field, it's specified as a length-2 array, which is the mask bitmask (located at the actual position in the register, such that just AND-ing it will mask out everything else but return a potentially shifted result), and offset which is how much to shift the masked result, to return a value that is the field but aligned to an unshifted position.

To access fields, extend the "pointer" class (is that possible? maybe by copying it or wrapping it) with the following methods:

  • .r() -- simply returns the 32-bit value at the pointer
  • .rm(mask) -- read with mask. Returns the bits at the specified mask position, shifted to the right
  • .rw(value, mask) -- read then write with mask. Reads register then modifies bits at masked location. Returns itself.
  • .ow(value, mask) -- overwrite. Don't read, just set all other bits to zero, except the value located in mask area.
  • .ow(value) -- overwrite. Just jam value into the register, don't do anything fancy

Worked Exmaples

A modestly simple register example

Simple Register Example

soc.svd gives us bit fields, but not memory regions or interrupts:

        <peripheral>
            <name>AUDIO</name>
            <baseAddress>0xF0016000</baseAddress>
            <groupName>AUDIO</groupName>
            <registers>
                <register>
                    <name>RX_CTL</name>
                    <description><![CDATA[Rx data path control]]></description>
                    <addressOffset>0x000c</addressOffset>
                    <resetValue>0x00</resetValue>
                    <size>32</size>
                    <access>read-write</access>
                    <fields>
                        <field>
                            <name>enable</name>
                            <msb>0</msb>
                            <bitRange>[0:0]</bitRange>
                            <lsb>0</lsb>
                            <description><![CDATA[Enable the receiving data]]></description>
                        </field>
                        <field>
                            <name>reset</name>
                            <msb>1</msb>
                            <bitRange>[1:1]</bitRange>
                            <lsb>1</lsb>
                            <description><![CDATA[Writing `1` resets the FIFO. Reset happens regardless of enable state.]]></description>
                        </field>
                    </fields>
                </register>
            </registers>
         </peripheral>

csr.csv gives us only names of whole registers and bases, but also gives us memory regions and interrupts:

csr_base,audio,0xf0016000,,
csr_register,audio_rx_ctl,0xf001600c,1,rw
constant,audio_interrupt,6,,
memory_region,audio,0xe0000000,4,io

proposed Rust-syntax accessors to be generated from a combination of the above two files:

    // this goes in betrusted_pac.rs
    const HW_AUDIO_BASE: u32 = 0xf001_6000;
    const HW_AUDIO_MEM: u32 = 0xe000_0000;
    const HW_AUDIO_CSR_LEN: u32 = 0x10; // do we need to round this up to a page? e.g. 0x1000
    const HW_AUDIO_MEM_LEN: u32 = 0x1;  // it's useful to know the actual length that's mapped if it's smaller than a page...
    const HW_AUDIO_RX_CTL: u32 = 0x3; // 0xc/4
    const HW_AUDIO_RX_CTL_ENABLE: [u32; 2] = [0b01; 0];
    const HW_AUDIO_RX_CTL_RESET: [u32; 2]  = [0b10; 1];
    
    // set up the base pointers (note "unsafe" blocks omitted for clarity)
    let audio_base = (HW_AUDIO_BASE as *mut u32) as *mut Volatile<u32>;
    let audio_mem = (HW_AUDIO_MEM as *mut u32) as *mut Volatile<u32>;

    // read RX_CTL
    let rx_ctl_value = (*audio_base.add(HW_AUDIO_RX_CTL)).r();
    let enable: bool = (*audio_base.add(HW_AUDIO_RX_CTL)).rm(HW_AUDIO_RX_CTL_ENABLE) != 0; // read based on mask
    let reset: bool = (*audio_base.add(HW_AUDIO_RX_CTL)).rm(HW_AUDIO_RX_CTL_RESET) != 0;

    (*audio_base.add(HW_AUDIO_RX_CTL)).ow(HW_AUDIO_RX_CTL_RESET); // sets the RESET bit, and also implicitly clears ENABLE
    (*audio_base.add(HW_AUDIO_RX_CTL)).ow(0, HW_AUDIO_RX_CTL_RESET_MASK); // dynamic dispatch is possible in Rust, yes?

    // these two proposals manipulates two bits
    (*audio_base.add(HW_AUDIO_RX_CTL))
       .rw(0, HW_AUDIO_RX_CTL_RESET)
       .rw(1, HW_AUDIO_RX_CTL_ENABLE);

    // actually same as above. The "False" can be dropped with no harm.
    (*audio_base.add(HW_AUDIO_RX_CTL))
       .rw(1, HW_AUDIO_RX_CTL_ENABLE);

    // write and read to the audio memory window
    (*audio_mem).write( audio_sample );
    let mic_data: u16 = (*audio_mem).read() as u16;

    // map to virtual memory
    let audio_base_vmem = xous::syscall::map_memory(
        xous::MemoryAddress::new(HW_AUDIO_BASE),
        None,
        HW_AUDIO_CSR_LEN, // do we need to round this up to a page?
        xous::MemoryFlags::R | xous::MemoryFlags::W,
    )
    .expect("couldn't map audio port");

     // example of using vmem to access using the same constants/crate
     let abase = (audio_base_vmem.as_ptr() as *mut u32) as *mut Voltaile<u32>;
     (*abase.add(HW_AUDIO_RX_CTL)).rw(1, HW_AUDIO_RX_CTL_ENABLE);
 
     // not addressed: interrupts??

A really simple register example

Simple Register Example
        <peripheral>
            <name>UART</name>
            <baseAddress>0xF0004000</baseAddress>
            <groupName>UART</groupName>
            <registers>
                <register>
                    <name>RXTX</name>
                    <addressOffset>0x0000</addressOffset>
                    <resetValue>0x00</resetValue>
                    <size>32</size>
                    <access>read-write</access>
                    <fields>
                        <field>
                            <name>rxtx</name>
                            <msb>7</msb>
                            <bitRange>[7:0]</bitRange>
                            <lsb>0</lsb>
                        </field>
                    </fields>
                </register>
             </registers>
         </peripheral>
csr_base,uart,0xf0004000,,
csr_register,uart_rxtx,0xf0004000,1,rw
    const HW_UART_BASE: u32 = 0xf004_0000;
    const HW_UART_RXTX: u32 = 0x0;
    const HW_UART_RXTX_RXTX: [u32; 2] = [0b1111_1111, 0];
    
    // example of the simplest type of register accessor
    let uart_base = (HW_UART_BASE as *mut u32) as *mut Volatile<u32>;

    let rx_char: u8 = (*uart_base.add(HW_UART_RXTX)).r() as u8;

    // these are identical in this case because the register is simple
    (*uart_base.add(HW_UART_RXTX)).ow(tx_char, HW_UART_RXTX_RXTX);
    (*uart_base.add(HW_UART_RXTX)).ow(tx_char); 

    (*uart_base.add(HW_UART_RXTX))
       .rw(tx_char, HW_UART_RXTX_RXTX); // this will read the register and throw away contents before replacing it

A difficult, compound register example

Simple Register Example
        <peripheral>
            <name>AUDIO</name>
            <baseAddress>0xF0016000</baseAddress>
            <groupName>AUDIO</groupName>
            <registers>
                <register>
                    <name>RX_STAT</name>
                    <description><![CDATA[Rx data path status]]></description>
                    <addressOffset>0x0010</addressOffset>
                    <resetValue>0x80000000</resetValue>
                    <size>32</size>
                    <access>read-only</access>
                    <fields>
                        <field>
                            <name>overflow</name>
                            <msb>0</msb>
                            <bitRange>[0:0]</bitRange>
                            <lsb>0</lsb>
                            <description><![CDATA[Rx overflow]]></description>
                        </field>
                        <field>
                            <name>underflow</name>
                            <msb>1</msb>
                            <bitRange>[1:1]</bitRange>
                            <lsb>1</lsb>
                            <description><![CDATA[Rx underflow]]></description>
                        </field>
                        <field>
                            <name>dataready</name>
                            <msb>2</msb>
                            <bitRange>[2:2]</bitRange>
                            <lsb>2</lsb>
                            <description><![CDATA[256 words of data loaded and ready to read]]></description>
                        </field>
                        <field>
                            <name>empty</name>
                            <msb>3</msb>
                            <bitRange>[3:3]</bitRange>
                            <lsb>3</lsb>
                            <description><![CDATA[No data available in FIFO to read]]></description>
                        </field>
                        <field>
                            <name>wrcount</name>
                            <msb>12</msb>
                            <bitRange>[12:4]</bitRange>
                            <lsb>4</lsb>
                            <description><![CDATA[Write count]]></description>
                        </field>
                        <field>
                            <name>rdcount</name>
                            <msb>21</msb>
                            <bitRange>[21:13]</bitRange>
                            <lsb>13</lsb>
                            <description><![CDATA[Read count]]></description>
                        </field>
                        <field>
                            <name>fifo_depth</name>
                            <msb>30</msb>
                            <bitRange>[30:22]</bitRange>
                            <lsb>22</lsb>
                            <description><![CDATA[FIFO depth as synthesized]]></description>
                        </field>
                        <field>
                            <name>concatenate_channels</name>
                            <msb>31</msb>
                            <bitRange>[31:31]</bitRange>
                            <lsb>31</lsb>
                            <description><![CDATA[Receive and send both channels atomically]]></description>
                        </field>
                    </fields>
                </register>
            </registers>
         </peripheral>
csr_base,audio,0xf0016000,,
csr_register,audio_rx_stat,0xf0016010,1,ro
constant,audio_interrupt,6,,
memory_region,audio,0xe0000000,4,io
    const HW_AUDIO_BASE: u32 = 0xf001_6000;
    const HW_AUDIO_MEM: u32 = 0xe000_0000;
    const HW_AUDIO_RX_STAT: u32 = 0x4; // 0x10/4
    const HW_AUDIO_RX_STAT_EMPTY: [u32; 2] = [0b1000, 3];
    const HW_AUDIO_RX_STAT_RDCOUNT: [u32; 2]  = [0b11_1111_1110_0000_0000_0000, 13];
    
    // set up the base pointers
    let audio_base = (HW_AUDIO_BASE as *mut u32) as *mut Volatile<u32>;
    let audio_mem = (HW_AUDIO_MEM as *mut u32) as *mut Volatile<u32>;

    // read RX_STAT
    let rx_stat = (*audio_base.add(HW_AUDIO_RX_STAT)).r(); // full 32 bit value
    let empty: bool = (*audio_base.add(HW_AUDIO_RX_STAT)).rm(HW_AUDIO_RX_STAT_EMPTY) != 0;
    // this will shift rdcount automatically
    let rdcount: usize = (*audio_base.add(HW_AUDIO_RX_STAT)).rm(HW_AUDIO_RX_STAT_RDCOUNT);

    // this overwrites the *entire* register with just WRCOUNT, and everything else as 0
    (*audio_base.add(HW_AUDIO_RX_STAT)).ow(value, HW_AUDIO_RX_STAT_RDCOUNT);

    // this will read the existing value and just update these two fields
    (*audio_base.add(HW_AUDIO_RX_STAT))
       .rw(value1, HW_AUDIO_RX_STAT_EMPTY)
       .rw(value2, HW_AUDIO_RX_STAT_RDCOUNT);

    // write and read to the audio memory window
    (*audio_mem).write( audio_sample );
    let mic_data: u16 = (*audio_mem).read() as u16;
 
     // not addressed: interrupts??