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 / 1psWhen you modify this code later for your needs, make sure you always confirm transaction by strobing IO_ready signal, or MCS will STALL FOREVER!
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
Here is a timing diagram for the bus (taken from PG111 document):
Here is the code for the main module:
`timescale 1ns / 1psAgain, not forget to wire new signals as you add them later.
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
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.