Thursday, March 14, 2013

Four neat examples of PSHDL in action

If you still don't know why you should use PSHDL, here are four neat examples of what PSHDL can do for you! Generally the amount of generated VHDL code is up to 5 times of the original PSHDL code. For generators however this ratio is even worse (or better depending on how you see it). If you want to see the generated VHDL code, just paste it into the online compiler at: pshdl.org.

First example: Registers

module Calculus {
    param uint WIDTH=8;
    in uint<WIDTH> a,b;
    out register uint<WIDTH> sum=a+b;
}

If you want to describe a register in VHDL, you will have to go trough some effort to actually describe how the register is supposed to work. This includes creating a process with proper clk and reset signals. In PSHDL a register is created by placing the register keyword in front of a variable. That variable then will be realized with a proper register description.

But where does the clock come from? Well, in PSHDL there is a special reference called $clk that, if it is referenced, creates a 1 bit in port. The default register, without any further refinement, uses this as the clock. Of course you are free to declare any port to become the default $clk or specify another signal as clock. You can also change on what edge the register is working, wether the reset is active low or high, synchronous or asynchronous, the reset value etc..

Another thing that is different from VHDL is that you can place your ports anywhere in the file. This allows you to place code together that logically belongs together. I can hear you think: But I actually like the port declaration at entity level! There are 2 solutions for your concern, firstly, you can simply place all your ports at the top of the file if you want to, secondly you can annotate your module so that the compiler automatically creates an interface for you that will sit atop the module. But more about that in a later blogpost.

The most important thing to remember about PSHDL is that it reverses the synthesis results from VHDL, it is easy to create a register, while it is not possible to create a latch (unless you explicitly tell the compiler to do so). To a seasoned VHDL coder, this may seem like a triviality, but be clear:
Unexpected latches are the number one reason student designs don't work.

Second example: Interfaces

The idea of a component, the declaration of ports without providing the actual implementation, is called interfaces. An interface can be declared and implemented by a module. It can also be instantiated.

module de.tuhh.ict.BitAdder {
    in bit a,b,cin;
    out bit sum=a^b^cin; 
    out bit cout=(a&b)^(cin&(a^b));
}

interface de.tuhh.ict.IBitAdder {
    in bit a,b,cin;
    out bit sum, cout;
}

module de.tuhh.ict.rippleCarry{
    param uint WIDTH=16;
    in uint<WIDTH> a, b;
    uint<WIDTH> cTemp;
    out register uint<WIDTH> sum;
    //Instantiate the other module
    BitAdder adder[WIDTH];
    //Uncomment to instantiate the interface 
    //(you have to make sure that the synthesis tool 
    //can find the actual implementation)
    // IBitAdder adder[WIDTH];

    adder[0].a=a{0};
    adder[0].b=b{0};
    adder[0].cin=0;
    sum{0}=adder[0].sum;
    cTemp{0}=adder[0].cout;

    for (I={1:WIDTH-1}){
        adder[I].a=a{I};
        adder[I].b=b{I};
        adder[I].cin=cTemp{I-1};
        cTemp{I}=adder[I].cout;
        sum{I}=adder[I].sum;
    }
}

If you are wondering what the curly braces are doing: They can be used to access bits. What you can also see is that it is easy to use arrays. You can create arrays of instances and variables and access them with the rectangular braces, just like in other C based languages.

But what is more interesting here is that you don't need a port map. You simply say: Instantiate me a BitAdder or as in this case, a whole lot of BitAdders. Then you can access the ports of it with the dot notation.

If you change the BitAdder instance to IBitAdder, it is your task to provide an implementation of it with the same name. This is how you can instantiate any VHDL, Verilog or other black box. For VHDL however you get tool support to automatically create the interface for you.

Third example: Statemachines

module de.tuhh.ict.Statemachine{
    enum States={IDLE, WAITING, DOSOMETHING}
    //Do not assign a default state here unless you want to 
    //define a new state in each case statement
    register enum States state;
    in bit a;
    out register bit b;
    out bit c;
    switch (state) {
    case States.IDLE:
        if (a)
            state=States.WAITING;
    //You can let the Enum. away in switch cases where 
    //switch operates on an enum
    case WAITING: 
        if (!a)
            state=DOSOMETHING;
    case States.DOSOMETHING:
        b=1;
        c=1;
        state=States.IDLE;
    default:
        state=States.IDLE;
    }

    out bit x=0;
    if (state==IDLE)
        x=1;
}

In PSHDL there is no such thing as processes. As such the clock domains are only separated by the definition of the register. This makes it possible to describe a mealy state-machine (one where the outputs depend directly on the inputs) without the need for multiple processes. This increases readability as everything that is related to the state-machine can be found in one place.

Fourth example: Generators

In most FPGA designs these days, you will find some kind of processor. To extend this processor IPCores are used to accelerate certain functions. PSHDL makes it very easy to get a proper IPCore.

package de.tuhh.ict;
module BUSTest{
    include Bus bus=generate plb()<[
        row input{
            rw register uint<16> a;
            rw register uint<16> b;
        }
        row output {
            fill;
            r register uint<16> result;
        }
        column adder {
            input;
            output;
        }
        memory {
            adder[4];
        }
    ]>;
    for (i={0:3}){
        bus.result[i]=bus.a[i]+bus.b[i];
    }
}

This little example here, generates the infrastructure for a PLB based IPCore. In case you are wondering, AXI and ABP are supported as well. This code generates a total of 8 registers. Using a memory map, a description on how the peripheral can be accessed, is a pretty advanced feature, but It allows PSHDL to generate some very useful support files like this HTML representation of the memory layout:

Offset3116150Row
adder [0]
0 [0x00]a [0]b [0]input [0]
4 [0x04]unusedresult [0]output [0]
adder [1]
8 [0x08]a [1]b [1]input [1]
12 [0x0c]unusedresult [1]output [1]
adder [2]
16 [0x10]a [2]b [2]input [2]
20 [0x14]unusedresult [2]output [2]
adder [3]
24 [0x18]a [3]b [3]input [3]
28 [0x1c]unusedresult [3]output [3]

The generated Interface looks like this:

interface Bus{
   in register uint<16> result[4];
   inout register uint<16> b[4];
   inout register uint<16> a[4];
}

And it also generates the C code that you need to access the SW registers:

//Typedef
typedef struct input {
    bus_uint16_t    a;
    bus_uint16_t    b;
} input_t;
// Setter
int setInputDirect(uint32_t *base, int index, bus_uint16_t a, bus_uint16_t b);
int setInput(uint32_t *base, int index, input_t *newVal);
//Getter
int getInputDirect(uint32_t *base, int index, bus_uint16_t *a, bus_uint16_t *b);
int getInput(uint32_t *base, int index, input_t *result);
//Typedef
typedef struct output {
    bus_uint16_t    result;
} output_t;
//Getter
int getOutputDirect(uint32_t *base, int index, bus_uint16_t *result);
int getOutput(uint32_t *base, int index, output_t *result);
typedef struct adder {
    input_t input;
    output_t output;
} adder_t;

And for printing it nicely to STDOUT:

void printInput(input_t *data);
void printOutput(output_t *data);

Additionally it will also create all the files that you need to place it into your local Xilinx IP directory and instantiate it directly.

No comments:

Post a Comment