APB read/write

Hi everyone,

I’m currently working on interfacing a custom APB peripheral with the processor on the BeagleV-Fire board, but I’m stuck trying to get any write to actually reach the memory-mapped register.

Here’s what I’ve done by now:

  1. I followed this tutorial https://www.youtube.com/watch?v=ZvXKUD36HSM&list=PLMQHM3yowLTuPqlwytGAeLyJ5eavtuSAJ&index=12&ab_channel=bustedwing but instead of reading what I’ve written, I get 0x00.

  2. I’ve also tried my own design:

module simple_add (
input         	  pclk          ,
input         	  presetn       ,
input      [31:0] paddr         ,
input      [31:0] pwdata        ,
input         	  pwrite        ,
input         	  psel          ,
input         	  penable       ,
output reg [31:0] prdata        ,
output reg        pready        
);

reg [31:0] a;
reg [31:0] b;
reg [31:0] rezultat;
reg [1:0] cnt;

always @(posedge pclk or negedge presetn)
	if (~presetn)	        pready <= 1'b0; else
	if (pready)						pready <= 1'b0; else
	if (psel & ~penable)	pready <= 1'b1;
	
always @(posedge pclk or negedge presetn)
	if (~presetn)	                                prdata <= 'd0; else
	if (psel & ~penable & ~pwrite & (cnt == 'd2)) prdata <= rezultat;	
	
always @(posedge pclk or negedge presetn)
	if (~presetn)	                        a <= 'd0; else
	if (psel & ~penable & ~|cnt & pwrite)	a <= pwdata;
	
always @(posedge pclk or negedge presetn)
	if (~presetn)	                                b <= 'd0; else
	if (psel & ~penable & (cnt == 'd1) & pwrite)	b <= pwdata;
	
always @(posedge pclk or negedge presetn)	
	if (~presetn)              	rezultat <= 'd0; else
	if (pready & (cnt == 'd1))	rezultat <= a + b;
	
always @(posedge pclk or negedge presetn)
	if (~presetn)	cnt <= 'd0; else
	if (pready)		cnt <= cnt + 'd1;
	
endmodule

And instantiated in CAPE.v:

wire sel_apb_yuv_rgb = APB_SLAVE_SLAVE_PSEL & (APB_SLAVE_SLAVE_PADDR[31:20] == 12'h411);
simple_add simple_add_i(
.pclk      (PCLK   ),
.presetn   (PRESETN),
.paddr     (APB_SLAVE_SLAVE_PADDR),
.pwdata    (APB_SLAVE_SLAVE_PWDATA),
.pwrite    (APB_SLAVE_SLAVE_PWRITE),
.psel      (sel_apb_yuv_rgb),
.penable   (APB_SLAVE_SLAVE_PENABLE),
.prdata    (APB_SLAVE_PRDATA),
.pready    (pready)
);

Mapped it at address: 0x41100000
And added this to the device tree overlay under fabric bus:

simple_add@41100000 {
    compatible = "generic-uio";
    reg = <0x00 0x41100000 0x00 0x1000>;
};

And the C program:

#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define APB_BASE_ADDR  0x41100000
#define MAP_SIZE       4096

int main() {
    int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (mem_fd < 0) {
        perror("open");
        return 1;
    }

    void *map_base = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, APB_BASE_ADDR);
    if (map_base == MAP_FAILED) {
        perror("mmap");
        close(mem_fd);
        return 1;
    }

    volatile uint32_t *reg_a = (volatile uint32_t *)(map_base + 0x10);
    volatile uint32_t *reg_b = (volatile uint32_t *)(map_base + 0x14);
    volatile uint32_t *reg_result = (volatile uint32_t *)(map_base + 0x18);

    *reg_a = 10;
    *reg_b = 15;

    // Delay 
    for (volatile int i = 0; i < 1000; ++i);

    uint32_t result = *reg_result;
    printf("Rezultat = %u\n", result);

    munmap(map_base, MAP_SIZE);
    close(mem_fd);
    return 0;
}

And still nothing.

  1. Used devmem2 to test memory directly — no effect.

I don’t know what should I do to simply read / write from mapped memory using APB. Can someone explain?

Also this is my GitLab project: https://git.beagleboard.org/Luciana.lm11/testing-gateware

It’s getting a little late for me to take a focused look,
but please adjust your message tags; you want fire, not ahead.

Looks interesting by the looks of it though…

Right. I thought your question felt familiar; we’ve had this discussion before!

Memory write - BeagleV - BeagleBoard

Did my suggestion not work for you?

Edit: I have been informed about the existence of further Documentation.
Perhaps some nugget can be gleaned from the following:
Accessing APB and AXI Peripherals Through Linux — BeagleBoard Documentation

1 Like

Update: Starting from your example I made this:

module ControlAPB #(
    parameter SELECT_BIT = 10
)(
    // Standard APB Interface (unchanged)
    input  wire         PCLK,
    input  wire         PRESETN,
    input  wire [31:0]  PADDR,
    input  wire         PENABLE,
    output wire [31:0]  PRDATA,
    output wire         PREADY,
    input  wire         PSEL,
    output wire         PSLVERR,
    input  wire [31:0]  PWDATA,
    input  wire         PWRITE,
    
    // Adder-specific outputs (simplified from original)
    output wire [31:0]  SUM_RESULT
);

// APB response signals (unchanged)
assign PSLVERR = 1'b0;
assign PREADY  = 1'b1;

// Input registers for our adder
reg [31:0] operand_a;
reg [31:0] operand_b;

// Write decoding
always @(posedge PCLK or negedge PRESETN) begin
    if (~PRESETN) begin
        operand_a <= 32'h0;
        operand_b <= 32'h0;
    end else if (PSEL && PENABLE && PWRITE) begin
        case (PADDR[7:4])
            4'h0: operand_a <= PWDATA;  // Write to operand A at address offset 0
            4'h4: operand_b <= PWDATA;  // Write to operand B at address offset 4
        endcase
    end
end

// Adder logic (combinational)
wire [31:0] sum = operand_a + operand_b;

// Read multiplexer
assign PRDATA = (PADDR[7:4] == 4'h8) ? sum : 32'h0;  // Read sum at address offset 8

// Output the sum continuously (optional)
assign SUM_RESULT = sum;

endmodule

And simply instantiated it in CAPE.v without checking the adrress.

wire [31:0] result;

ControlAPB #(
.SELECT_BIT (10)
) ControlAPB_i (
    // Standard APB Interface (unchanged)
.PCLK       (PCLK),
.PRESETN    (PRESETN),
.PADDR      (APB_SLAVE_SLAVE_PADDR),
.PENABLE    (APB_SLAVE_SLAVE_PENABLE),
.PRDATA     (APB_SLAVE_PRDATA),
.PREADY     (),
.PSEL       (APB_SLAVE_SLAVE_PSEL),
.PSLVERR    (),
.PWDATA     (APB_SLAVE_SLAVE_PWDATA),
.PWRITE     (APB_SLAVE_SLAVE_PWRITE),
    // Adder-specific outputs (simplified from original)
.SUM_RESULT  (result)
);

Also added the first 8 bits of the result on leds like this:

assign GPIO_OE_net_0  = { 16'h0000 , GPIO_OE[27:15], 8'hFF, GPIO_OE[6:0]};
assign GPIO_OUT_net_0 = { 16'h0000 , GPIO_OUT[27:15], result[7:0], GPIO_OUT[6:0] };

Then executed the C code from this post, but changed the offset for a - 0x00 (a=3), for b - 0x40 (b=2) and for result - 0x80 (expected 5). The leds are turned on correctly, but now I have to manage the interrupt part to flag when it’s the right time to read the result from memory and print it in the console.

Also, why isn’t PREADY connected to any output signal like PRDATA? And why it’s always 1, in the actual APB protocol it sets when PSEL & ~PENABLE, and resets after one period, like this:

If you take another look at PREADY, there are actually two allowed states,
as indicated by the solid line at HIGH. The line can go LOW if the initiator has to wait,
but if the slave is ready to provide data all the time, PREADY never needs to be LOW.

Your combinatorial logic is always ready; hence the assign PREADY = 1'b1;.

Once you start to make calculations or anything asynchronous,
then you’re right. Then you need to pay attention to PREADY.

Found the problem :
.PRDATA (APB_SLAVE_PRDATA),
should actually be:
.PRDATA (APB_SLAVE_SLAVE_PRDATA),

Happy days!