Pico Blaze Verilog Demo

Author: Scott Gravenhorst

Spartan-3E Starter Kit and WebPack ISE software is assumed for this project.

This describes a design using the PicoBlaze microcontroller to manipulate the Spartan-3E Starter Kit LEDs in a pattern like the Cylon robots of Battlestar Galactica. It is meant to demonstrate how to use Verilog to instantiate a kcpsm3 PicoBlaze microcontroller and it's assembled program ROM and connect that with enough FPGA logic to allow the microcontroller to send data to the LEDs in a pattern controlled by an assembly language program. This is a very simple and rudimentary working example.

The first step is to create a project. I used WebPack ISE's Project Add New Source and created the Verilog and user constraint files (PBcylon_Main.v and PBcylon_Main.ucf). I used a plain text editor to create the PBcylon.psm PicoBlaze source code file.

The assembler and the microcontroller object file (kcpsm3.v) can be found in the KCPSM3.zip file. You will also need the JTAG_Loader_ROM_form.v and JTAG_Loader_ROM_form.vhd files, rename them to ROM_form.v and ROM_form.vhd placeing them in your project directory. These files are found in KCPSM3.zip. You can get KCPSM3.zip from the Xilinx website.

Create pbcylon.psm and assemble it with kcpsm3.exe, found in the zip file. The assembly process creates pbcylon.v and pbcylon.vhd. Once you have created these by running the assembler, you need to add one of these to your project, I include pbcylon.v because I use Verilog. A pbcylon ROM object is instantiated in main.v which is connected to the PicoBlaze microcontroller through wires 'address' and 'instruction'. The PicoBlaze object 'out_port' connects to 8 registers (LED_reg) which maintain the state of the Starter Kit LEDs.

Once that is complete, you should be able to compile and produce a .bit file to send to your Spartan-3E Starter Kit using the iMPACT tool provided with ISE. It will immediately begin displaying the LED pattern. The LCD display stalls with whatever data was in it at the time PBcylon started.

From the source comments you should be able to see how you can create your own designs using the PicoBlaze embedded microcontroller.

One of the things that confused me while putting this together was that while I had instantiated the kcpsm3 and the pbcylon modules, I made the erroneous assumption that they would be connected to each other because identifiers for address, instruction and other signals were the same for both modules. The compiler was happy enough with this. What actually happened is that wire objects were created, but they weren't correct in size, they default to a single wire where 'address' and 'instruction' are vectors. Once I realized this, I added specific definitions for wire objects address, instruction and out_port, describing them as vectors identical to the declarations found in each of those modules. Then the compiler correctly connected the components and the whole thing started working.

This is the startup project file (PBcylon_Main.v):

=========================================================================================================

`timescale 1ns / 1ps
// PBcylon.v
// Scott R. Gravenhorst
// PicoBlaze Cylon LED Display
// 07-01-2006

// I'm an old C guy, so this is named "Main"...
module Main( led, clk );             // led is the same 'led' in PBcylon_Main.ucf
    output [7:0] led;                // connection to LEDs
    input clk;                       // 50MHz clock
    reg [7:0] LED_reg;               // A flipflop bank (8) to hold LED states
    wire [9:0] address;              // wires to connect uC address lines to ROM
    wire [17:0] instruction;         // wires to connect uC data lines, to ROM
    wire [7:0] out_port;             // wires to connect uC output port to the LED flipflops

    // Instantiate the uC (kcpsm3)  This uses file kcpsm3.v
    kcpsm3 mcu( address, instruction, port_id, write_strobe, out_port, read_strobe, in_port, interrupt, interrupt_ack, reset, clk );

    // Instantiate the pbcylon ROM which contains the assembled pbcylon uC code.
    // This uses file pbcylon.v (see text regarding assembly).
    pbcylon CylonProcess( address, instruction, proc_reset, clk );

    assign interrupt = 0;            // ground the interrupt line
    assign led = LED_reg;            // Connect the outputs of the LED state flipflops to the LEDs

    always @ ( posedge clk )         // on each positive edge of the 50 MHz clock
      begin
        if ( write_strobe == 1'b1 )  // if write strobe is high, then out_port data is valid NOW
          LED_reg = out_port;        // so send out_port data to the LED state flipflops' inputs
        end
endmodule
// Note that because only one PicoBlaze output port is used,
// I did not need to include any port decoding logic

=========================================================================================================

Now the UCF file (PBcylon_Main.ucf):

=========================================================================================================

# Period constraint for 50MHz operation
#
NET "clk" PERIOD = 20.0ns HIGH 50%;
#
# soldered 50MHz Clock.
# 
NET "clk" LOC = "C9" | IOSTANDARD = LVTTL;
#
# Define the pins for the eight LEDs, giving them the name 'led'
NET "led<0>" LOC = "F12" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<1>" LOC = "E12" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<2>" LOC = "E11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<3>" LOC = "F11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<4>" LOC = "C11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<5>" LOC = "D11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<6>" LOC = "E9"  | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;
NET "led<7>" LOC = "F9"  | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 4;

=========================================================================================================

And finally the pbcylon.psm file:

=========================================================================================================

                  ;Control of LEDs and LCD as a cylon display.  Very simple.
                  ;Uses KCPSM3 module.
                  ;
                  ;Version v1.00 - 01 July 2006
                  ;
                  ;**************************************************************************************
                  ;Useful data constants
                  ;**************************************************************************************
                  ;
                  ;**************************************************************************************
                  ;
                  ; Delay routines were taken from the s3esk_startup reference design project
                  ;
                  ;**************************************************************************************
                  ;The main operation of the program uses 1ms delays to set the shift rate
                  ;of the LCD display. A 16-bit value determines how many milliseconds
                  ;there are between shifts
                  ;
                  ;Tests indicate that the fastest shift rate that the LCD display supports is
                  ;500ms. Faster than this and the display becomes less clear to read.
                  ;
                  CONSTANT shift_delay_msb, 01        ;delay is 500ms (01F4 hex)
                  CONSTANT shift_delay_lsb, F4
                  ;
                  ;
                  ;Constant to define a software delay of 1us. This must be adjusted to reflect the
                  ;clock applied to KCPSM3. Every instruction executes in 2 clock cycles making the
                  ;calculation highly predictable. The '6' in the following equation even allows for
                  ;'CALL delay_1us' instruction in the initiating code.
                  ;
                  ; delay_1us_constant =  (clock_rate - 6)/4       Where 'clock_rate' is in MHz
                  ;
                  ;Example: For a 50MHz clock the constant value is (10-6)/4 = 11  (0B Hex).
                  ;For clock rates below 10MHz the value of 1 must be used and the operation will
                  ;become lower than intended.
                  ;
                  CONSTANT delay_1us_constant, 0B
                  ;
                  ;
                  ;
                  ;**************************************************************************************
                  ;Initialise the system
                  ;**************************************************************************************
                  ;

                  NAMEREG s8, cylon
                  NAMEREG s9, direction

      cold_start: DISABLE INTERRUPT
                  LOAD cylon, 04                         ; s8 is the LED registar.
                  LOAD direction, 00                     ; direction of LED travel

                  ;**************************************************************************************
                  ;Main program - logic to move and reverse direction of illuminated LED
                  ;**************************************************************************************

            loop: OUTPUT cylon, 00                       ; send the LED data to port 00, no decoder is wired...
                  CALL delay_20ms                        ; delay 20 milliseconds
                  CALL delay_20ms                        ; delay 20 milliseconds

                  COMPARE direction, 00                  ; compare direction bit (bit 0 of s9)
                  JUMP Z, GoLeft                         ; go right if it was zero
                  JUMP GoRight                           ; go left if it was not zero

          GoLeft: COMPARE cylon, 80                      ; is the bit at the left end?
                  JUMP Z, AtLeftEnd                      ; if yes, then go right

        MoveLeft: SL0 cylon                              ; shift cylon left feeding zeroes from the right
                  JUMP loop                              ; do it again

         GoRight: COMPARE cylon, 01                      ; is the bit at the right end?
                  JUMP Z, AtRightEnd                     ; Are we at the right end?

       MoveRight: SR0 cylon                              ; shift cylon right feeding zeroes from the left
                  JUMP loop                              ; do it again

       AtLeftEnd: XOR direction, 01                      ; change direction
                  JUMP MoveRight                         ; move to the right

      AtRightEnd: XOR direction, 01                      ; change direction
                  JUMP MoveLeft                          ; move to the left

                  ;**************************************************************************************
                  ;Software delay routines
                  ;**************************************************************************************
                  ;
                  ;Delay of 1us.
                  ;
                  ;Constant value defines reflects the clock applied to KCPSM3. Every instruction
                  ;executes in 2 clock cycles making the calculation highly predictable. The '6' in
                  ;the following equation even allows for 'CALL delay_1us' instruction in the initiating code.
                  ;
                  ; delay_1us_constant =  (clock_rate - 6)/4       Where 'clock_rate' is in MHz
                  ;
                  ;Registers used s0
                  ;
       delay_1us: LOAD s0, delay_1us_constant
        wait_1us: SUB s0, 01
                  JUMP NZ, wait_1us
                  RETURN
                  ;
                  ;Delay of 40us.
                  ;
                  ;Registers used s0, s1
                  ;
      delay_40us: LOAD s1, 28                         ;40 x 1us = 40us
       wait_40us: CALL delay_1us
                  SUB s1, 01
                  JUMP NZ, wait_40us
                  RETURN
                  ;
                  ;
                  ;Delay of 1ms.
                  ;
                  ;Registers used s0, s1, s2
                  ;
       delay_1ms: LOAD s2, 19                         ;25 x 40us = 1ms
        wait_1ms: CALL delay_40us
                  SUB s2, 01
                  JUMP NZ, wait_1ms
                  RETURN
                  ;
                  ;Delay of 20ms.
                  ;
                  ;Delay of 20ms used during initialisation.
                  ;
                  ;Registers used s0, s1, s2, s3
                  ;
      delay_20ms: LOAD s3, 14                         ;20 x 1ms = 20ms
       wait_20ms: CALL delay_1ms
                  SUB s3, 01
                  JUMP NZ, wait_20ms
                  RETURN
                  ;
                  ;Delay of approximately 1 second.
                  ;
                  ;Registers used s0, s1, s2, s3, s4
                  ;
        delay_1s: LOAD s4, 32                         ;50 x 20ms = 1000ms
         wait_1s: CALL delay_20ms
                  SUB s4, 01
                  JUMP NZ, wait_1s
                  RETURN

=========================================================================================================