This project demonstrates how to implement a MicroBlaze MCS (Micro Controller System) soft-core processor on the Digilent Basys 3 FPGA board, running a bare-metal C application that drives a 4-digit seven-segment counter (0000–9999). The project is a practical introduction to embedded soft-core processor design on FPGAs, bridging the gap between pure HDL design and software-driven embedded systems.
The MicroBlaze MCS is AMD/Xilinx's all-in-one embedded processor IP — it packages the MicroBlaze CPU core, local BRAM memory, and configurable GPIO into a single block, making it ideal for simple control applications without the complexity of a full AXI-based system.
Why MicroBlaze MCS?When you need a simple microcontroller on an FPGA, you have several options: write everything in HDL, use the full MicroBlaze with AXI interconnect, or use the MicroBlaze MCS. The MCS variant is perfect for beginners and small projects because:
- Single IP block — no AXI interconnect, no address map headaches
- Built-in BRAM — up to 64KB on Artix-7, no external memory needed
- Integrated GPIO — up to 4 channels, configurable as input or output
- C programming — write your application logic in C, not Verilog
The design uses the MicroBlaze MCS with four GPIO channels:GPIO1 drives the seven-segment cathodes (active-low encoding)
- GPIO2 controls the anode multiplexing (one digit at a time)
- GPIO3 reads the 16 slide switches for runtime configuration
- GPIO4 reads the 5 push buttons for counter control
The Basys 3 has 4 common-anode, multiplexed seven-segment displays. You can't drive all 4 digits simultaneously — instead, you rapidly cycle through each digit (one at a time), switching the anode enable and cathode pattern fast enough that persistence of vision creates the illusion of all 4 digits being lit. The C code handles this multiplexing in a tight loop with ~2.5ms per digit, giving a ~100Hz refresh rate.
Key implementation details:
- Active-low encoding: On the Basys 3, writing a '0' to a cathode turns the segment ON
- Anode selection: Writing a '0' to an anode enables that digit
- Anti-ghosting: All anodes are turned off briefly before switching to prevent bleed-through
The C application provides interactive control via switches and buttons:
Control Function
SW[0] Count direction: 0 = up, 1 = down
SW[7:1] Speed control (0 = fast, 127 = slow)
btnU Reset counter to 0000
btnD Reset counter to 9999
btnL Hold to pause counting
Hardware SetupStep 1: Create Vivado Project- Open Vivado 2024.1 → Create Project
- Select Basys 3 board (xc7a35tcpg236-1)
- Create Block Design → Add MicroBlaze MCS IP
- Input Clock: 100 MHz
- Memory Size: 64 KB
- GPIO1: 8-bit output (segments)
- GPIO2: 4-bit output (anodes)
- GPIO3: 16-bit input (switches)
- GPIO4: 5-bit input (buttons)
A Verilog wrapper connects the MCS GPIO ports to physical Basys 3 pins:
module basys3_mcs_top(
input wire clk,
input wire btnC, // Reset
output wire [6:0] seg, // Cathodes (active low)
output wire dp, // Decimal point
output wire [3:0] an, // Anodes (active low)
input wire [15:0] sw,
input wire btnU, btnL, btnR, btnD
);
wire [7:0] GPIO1_0_tri_o;
wire [3:0] GPIO2_0_tri_o;
wire [15:0] GPIO3_0_tri_i;
wire [4:0] GPIO4_0_tri_i;
assign seg = GPIO1_0_tri_o[6:0];
assign dp = GPIO1_0_tri_o[7];
assign an = GPIO2_0_tri_o;
assign GPIO3_0_tri_i = sw;
assign GPIO4_0_tri_i = {btnC, btnU, btnL, btnR, btnD};
mcs_system_wrapper u_mcs (
.Clk_0 (clk),
.Reset_0 (btnC),
.GPIO1_0_tri_o (GPIO1_0_tri_o),
.GPIO2_0_tri_o (GPIO2_0_tri_o),
.GPIO3_0_tri_i (GPIO3_0_tri_i),
.GPIO4_0_tri_i (GPIO4_0_tri_i)
);
endmoduleImportant note on port names: Vivado appends _0 to all external port names when using "Make External" on block design pins. Always check the auto-generated mcs_system_wrapper.v for the exact names.
Apply the Basys 3 XDC constraints mapping to physical pins (W5 for clock, W7/W6/U8/V8/U5/V5/U7 for segments, U2/U4/V4/W4 for anodes, etc.)
Step 5: Generate Bitstream & Export HardwareRun Synthesis → Implementation → Generate Bitstream → Export Hardware (.xsa) with bitstream included.
Software DevelopmentStep 6: Launch Vitis ClassicImportant: Use Vitis Classic (not the Unified IDE) for MicroBlaze MCS projects in 2024.1. The Unified IDE has known BSP generation issues with MCS.
Launch via: Start Menu → Vitis Classic 2024.1
Step 7: Create Application- File → New → Application Project
- Select your exported.xsa file
- Processor: microblaze_mcs_0_microblaze_I
- OS: standalone
- Template: Empty Application (C)
The core of the application is a multiplexed display driver with a counter:
#include "xparameters.h"
#include "xiomodule.h"
static XIOModule io_mod;
// Seven-segment LUT (active LOW for Basys 3 common anode)
static const uint8_t SEG7_LUT[10] = {
0x40, 0x79, 0x24, 0x30, 0x19, // 0-4
0x12, 0x02, 0x78, 0x00, 0x10 // 5-9
};
static const uint8_t ANODE_SEL[4] = {
0x0E, 0x0D, 0x0B, 0x07 // One-hot inverted
};
static void display_one_cycle(uint8_t digits[4]) {
for (int i = 0; i < 4; i++) {
XIOModule_DiscreteWrite(&io_mod, 2, 0x0F); // All off
XIOModule_DiscreteWrite(&io_mod, 1,
SEG7_LUT[digits[i]] | 0x80); // Set segments
XIOModule_DiscreteWrite(&io_mod, 2, ANODE_SEL[i]); // Enable digit
delay_us(2500); // ~2.5ms hold
}
}
int main(void) {
uint16_t counter = 0;
XIOModule_Initialize(&io_mod, XPAR_IOMODULE_0_DEVICE_ID);
while (1) {
uint8_t digits[4];
digits[0] = counter % 10;
digits[1] = (counter / 10) % 10;
digits[2] = (counter / 100) % 10;
digits[3] = (counter / 1000) % 10;
display_one_cycle(digits);
counter++;
if (counter > 9999) counter = 0;
}
}Step 9: Build, Associate ELF, Program- Build in Vitis → generates.elf file
- In Vivado: Tools → Associate ELF Files → select the.elf
- Re-generate Bitstream (now includes C program in BRAM)
- Program the Basys 3 via Hardware Manager
1. Vivado port name mismatch: Vivado's block design automation appends _0 to external port names. Always verify against the generated wrapper file before writing your top-level module.
2. Vitis IDE selection matters: The Unified IDE (VS Code-based) in Vitis 2024.1 has BSP generation failures with MicroBlaze MCS on Windows due to path length issues. Use Vitis Classic mode instead.
3. Windows path length: Keep your project path short (e.g., C:\fpga\mcs\) — deeply nested paths exceed Windows' 260-character limit and cause build failures.
4. Active-low display encoding: The Basys 3 seven-segment display is common anode, active low. A '0' turns a segment ON. Getting this wrong produces garbage on the display.
5. Don't add C files to Vivado: The C source code belongs in Vitis only. Vivado handles hardware (Verilog/VHDL + block design). Vitis handles software (C code + BSP).









Comments