Welcome to my website!
This lab explores the fundamentals of Memory-Mapped I/O (MMIO) by creating a direct interface between an STM32 microcontroller and a GOWIN FPGA. The lab demonstrates how memory access operations can be used to control external hardware and read input states.
The lab uses a custom interposer board to connect the STM32 Nucleo and the GOWIN Tang Nano FPGA. This setup establishes a parallel bus interface between the two devices through the STM32’s FMC controller.
The FPGA and STM32 connected via the interposer board
The connections between the STM32 and FPGA include:
Logic analyzer connections to the memory interface signals
The first implementation was a simple write register that allows the STM32 to control an LED on the FPGA. This involves using a D-latch in the FPGA to store the value written to a specific memory address.
module my_mmio(
input NE1, // Chip select for address range 0x60000000-0x63FFFFFF
input NWE, // Write enable (active low)
input NOE, // Output enable
output NWAIT, // Wait signal (unused)
input [3:0] ADDR, // Address bus
inout [7:0] DATA, // Data bus
output reg LED_B // Blue LED output
);
assign NWAIT = 1;
wire d_latch_enable;
assign d_latch_enable = ~NE1 & ~NWE;
always@(d_latch_enable or DATA) begin
if(d_latch_enable) begin
LED_B <= DATA[0];
end
end
endmodule
The C code to control this LED looks like:
// Turn LED on
*((uint8_t *)0x60000000) = 0x01;
// Delay
for (int i = 0; i < 1000000; i++);
// Turn LED off
*((uint8_t *)0x60000000) = 0x00;
Logic analyzer capture of a write transaction
The second part involved implementing a read register that allows the STM32 to read the state of a push button on the FPGA. This uses a tri-state buffer to place the button state on the data bus when the specific memory address is read.
// Additional code for the read functionality
wire read_switch_enable;
assign read_switch_enable = ~NE1 & NWE & (ADDR == 4);
assign DATA[0] = (read_switch_enable) ? PB_A : 'bz;
The C code to read this switch:
uint8_t x;
x = *((uint8_t *)0x60000004); // Reading the switch state
Logic analyzer capture of a read transaction
The final implementation combined both read and write functionality, allowing control of all three RGB LED colors and reading both push buttons on the FPGA:
module my_mmio(
input NE1, // Chip select
input NWE, // Write enable
input NOE, // Output enable
output NWAIT, // Wait bus
input [3:0] ADDR, // Address bus
inout [7:0] DATA, // Bidirectional data bus
input PB_A, // Push button A
input PB_B, // Push button B
output reg LED_B, // Blue LED
output reg LED_R, // Red LED
output reg LED_G // Green LED
);
assign NWAIT = 1;
wire write_enable, read_enable;
// Write to LEDs when address is 0
assign write_enable = ~NE1 & ~NWE & (ADDR == 0);
// Read buttons when address is 4
assign read_enable = ~NE1 & NWE & (ADDR == 4);
// Drive data bus during reads
assign DATA[0] = (read_enable) ? PB_A : 1'bz;
assign DATA[1] = (read_enable) ? PB_B : 1'bz;
// Latch LED values during writes
always@(write_enable or DATA) begin
if(write_enable) begin
LED_B <= DATA[0];
LED_R <= DATA[1];
LED_G <= DATA[2];
end
end
endmodule
The C code was enhanced to cycle through colors and adjust timing based on button presses:
#define DELAY_BASE 10000 // Base delay amount
volatile int delay = DELAY_BASE; // Current delay
while (1) {
// Read buttons
uint8_t buttons = *((uint8_t *)0x60000004);
// Check for button A press - increase delay
if((buttons & 0x01)) {
delay += DELAY_BASE/10;
}
// Check for button B press - decrease delay
if((buttons & 0x02)) {
delay -= DELAY_BASE/10;
if(delay < DELAY_BASE/10)
delay = DELAY_BASE/10; // Minimum delay
}
// Cycle through colors
*((uint8_t *)0x60000000) = 0x01; // Blue
for(volatile int i = 0; i < delay; i++);
*((uint8_t *)0x60000000) = 0x02; // Red
for(volatile int i = 0; i < delay; i++);
*((uint8_t *)0x60000000) = 0x04; // Green
for(volatile int i = 0; i < delay; i++);
}
The lab successfully demonstrated key MMIO concepts:
The lab also explored the behavior of different memory access widths (byte, half-word, word) and observed the resulting bus transactions:
Logic analyzer capture showing multiple transactions for a 32-bit word access