Monday, April 17, 2017

Creating Vivado project with Microblaze MCS IP using code modules only.


Turns out not everyone knows that it's possible to create a project in Vivado IDE with Microblaze MCS IP (simple 32-bit microcontroller core) using only code (as apparently using visual designer is too hard for some designers). Below are detailed instructions. This was implemented in Vivado 2016.4, and designed for (and verified on) excellent Arty board from Digilent.

1. Creating a new project.


The name is not important - you can type in any one you like.


This step assumes you've already set up board support files for Arty board. If not - there are instructions on Digilent website on how to do that.


2. Generating Microblaze MCS IP core.








If you want/need to use other peripherals, you can enable them at this step. Just don't forget to wire up any extra signals in the top module.

3. Adding modules' source code.









Here is the source code for that module:
`timescale 1ns / 1ps
module IOBusLEDDriver(
            input sys_clock,
            input IO_addr_strobe,
            input [31:0]IO_address,
            input [3:0]IO_byte_enable,
            output reg[31:0] IO_read_data,
            input IO_read_strobe,
            output reg IO_ready,
            input [31:0]IO_write_data,
            input IO_write_strobe,
            output reg [3:0] LED
    );
    //IO bus is mapped into 0xc0000000 to 0xffffffff
    always @(posedge sys_clock) begin
        if (IO_addr_strobe) begin //we have some transaction
            if (IO_write_strobe) begin //it is a write transaction
                LED[IO_address[3:2]] <= IO_write_data[0];
            end
            if (IO_read_strobe) begin //it is a read transaction
                IO_read_data <= {24'h000000, LED[IO_address[3:2]]};
            end
            //mark transaction as completed
            IO_ready <= 1;
        end
        else
            IO_ready <= 0;
    end
endmodule 
When you modify this code later for your needs, make sure you always confirm transaction by strobing IO_ready signal, or MCS will STALL FOREVER!

Here is a timing diagram for the bus (taken from PG111 document):


Here is the code for the main module:
`timescale 1ns / 1ps
module TopModule(
        input sys_clock, //pin E3, LVCMOS33
        input reset, //active low, pin C2, LVCMOS33
        input usb_uart_rxd, //pin A9, LVCMOS33
        output usb_uart_txd, //pin D10, LVCMOS33
        output [3:0] LED //pins T10, T9, J5, H5 for LD7..LD4 respectively, all LVCMOS33
    );
 
    wire reset_h; assign reset_h = !reset;
 
    wire IO_addr_strobe;
    wire [31:0]IO_address;
    wire [3:0]IO_byte_enable;
    wire [31:0]IO_read_data;
    wire IO_read_strobe;
    wire IO_ready;
    wire [31:0]IO_write_data;
    wire IO_write_strobe;
 
    microblaze_mcs_0 mcs(
    .Clk(sys_clock),
    .Reset(reset_h),
 
    .IO_addr_strobe(IO_addr_strobe),
    .IO_address(IO_address),
    .IO_byte_enable(IO_byte_enable),
    .IO_read_data(IO_read_data),
    .IO_read_strobe(IO_read_strobe),
    .IO_ready(IO_ready),
    .IO_write_data(IO_write_data),
    .IO_write_strobe(IO_write_strobe),
 
    .UART_rxd(usb_uart_rxd),
    .UART_txd(usb_uart_txd)
    );
 
    IOBusLEDDriver io_periph(
        .sys_clock(sys_clock),
        .IO_addr_strobe(IO_addr_strobe),
        .IO_address(IO_address),
        .IO_byte_enable(IO_byte_enable),
        .IO_read_data(IO_read_data),
        .IO_read_strobe(IO_read_strobe),
        .IO_ready(IO_ready),
        .IO_write_data(IO_write_data),
        .IO_write_strobe(IO_write_strobe),
        .LED(LED)
    );
endmodule 
Again, not forget to wire new signals as you add them later.
Here we verify that Vivado recognized top module.


4. Assigning package pin to top level signals and generating bitstream.





Once synthesis is completed, open synthesized design to assign pins.
It is VERY IMPORTANT to assign correct pins, as incorrect assignments may damage some board components!

Now it's time to generate bitstream which is the final step on the hardware side of project.






5. Exporting hardware into SDK and creating software.







Again, project name is not important here.



Here is the code for the main firmware:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xiomodule_l.h"

int main()
{
    init_platform();

    print("Hello World\r\n");
    char c = '\0';
    while (c != 'q')
    {
print("Press key 1..8\r\n");
volatile int * ioBase = (int *)XPAR_IOMODULE_0_IO_BASEADDR;
c = XIOModule_RecvByte(STDIN_BASEADDRESS);
switch(c)
{
case '1':
*ioBase = *ioBase | 0x00000001;
break;
case '5':
*ioBase = *ioBase & ~0x00000001;
break;

case '2':
ioBase+=1;
*ioBase = *ioBase | 0x00000001;
break;
case '6':
ioBase+=1;
*ioBase = *ioBase & ~0x00000001;
break;

case '3':
ioBase+=2;
*ioBase = *ioBase | 0x00000001;
break;
case '7':
ioBase+=2;
*ioBase = *ioBase & ~0x00000001;
break;

case '4':
ioBase+=3;
*ioBase = *ioBase | 0x00000001;
break;
case '8':
ioBase+=3;
*ioBase = *ioBase & ~0x00000001;
break;
}
print("\r\n");
    }
    cleanup_platform();
    return 0;
}






That's all there's to it! I hope it was helpful for someone. Feel free to let me know if you have any question.