Running a PicoBlaze microcontroller on the Zedboard

2017-06-08

PicoBlaze is a 8-bit microcontroller that can be embedded on Xilinx FPGAs. The Zynq already contains a powerful dual core 32 bit microcontroller. One can wonder why wasting some programmable logic to implement such a tiny microcontroller ?

The PicoBlaze is very simple. It fetches 18-bit instructions from Block RAM and executes them in exactly 2 clock cycles. It can be used to implement state machines that would have been too complex to develop on hardware.

For reprogramming the PicoBlaze, the usual flow is to compile PicoBlaze assembly code with a Windows executable and then reflash the BRAM by JTAG [1]. Thanks to an open source project called Open PicoBlaze Assembler, it is now possible to compile on any platform with a Python interpreter.

Our goal here is to compile and flash the Picoblaze with a simple Python script. Below is the Vivado block design:

PicoBlaze Zedboard Vivado block design

The first step is to create a core with the VHDL source file kcpsm6.vhd written by Ken Chapman from Xilinx. The diagram below shows the connections between the PicoBlaze and the Block RAM.

PicoBlaze block design zoom

The BRAM is configured as a Stand Alone, True Dual Port RAM, with a width of 18 bits and a depth of 2048. This fits perfectly in one 36kb Block RAM of the Zynq. This would theoretically allow to put up to 140 PicoBlaze cores on the Zedboard!

The program is loaded from BRAM PORT A through an AXI4 Lite interface and read by the Picoblaze from PORT B. Below is a Python code that compiles, loads and executes 100 successive programs on the Picoblaze in 2.5 seconds total.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from koheron import connect, command
import os
import subprocess

class Picoblaze(object):
    def __init__(self, client):
        self.client = client

    @command()
    def write_ram(self, address, value):
        pass

    @command()
    def reset(self):
        pass

    @command()
    def set_input(self, value):
        pass

    @command()
    def get_output(self):
        return self.client.recv_uint32()

host = os.getenv('HOST','192.168.1.23')
client = connect(host, name='picoblaze')
driver = Picoblaze(client)

code = """
input s0, 0
add s0, {:x}
output s0, 0
"""

for i in range(100):

    with open("test.psm", "w") as f:
        f.write(code.format(i))

    # Compile test.psm with Open PicoBlaze Assembler
    subprocess.call(['opbasm', '--pb6', '--scratch-size=64', '--hex', '--mem-size=32', '--quiet', 'test.psm'])

    # Write program to BRAM
    with open('test.hex', 'r') as f:
        for j, line in enumerate(f):
            driver.write_ram(4*j, (int(line, 16)))

    driver.set_input(2)
    #driver.reset() # Restart picoblaze
    print i, driver.get_output()
    assert(driver.get_output() == 2 + i)

It is a stupid use of the PicoBlaze but it shows what could be done on the Zynq with Open Picoblaze Assembler.

FPGA design source code

https://github.com/Koheron/koheron-sdk/tree/master/examples/zedboard/picoblaze

References

[1]: Adam P. Taylor, Getting the Most out of Your PicoBlaze Microcontroller. Xilinx XCell journal, issue 89.
[2]: Open PicoBlaze Assembler

Also tagged FPGA

[email protected]