PSHDL is a hardware description language that focuses on simplicity and extensibility.
Each PSHDL file starts with a few lines of information.
A PSHDL file does not necessarily contain a module. It can solely serve as container for interface or enum declarations.
package de.tuhh.ict; module Test { //Statements go here }
This defines a new module Test within the package de.tuhh.ict.
package de.tuhh.ict; module Test{ import VHDL.work.*; import net.kbsvn.utils.ClockSync; }
This module imports some other packages. The VHDL.work package is where VHDL files are automatically imported into. The net.kbsvn.utils.ClockSync explicitly imports a certain module/interface from the package net.kbsvn.utils.
In the body of a PSHDL module you can find the following elements:
Each of which will be explained in the following sections
A Statement does not return a value. There are the following statements:
A assignment assigns a variable a new value. It always has the form «variableReference»«assignmentOp»«Expression». It is important to understand that the assignment that gets evaluated the closest to the bottom of the file is the assignment that determines the new value of the variable.
= | Simply assigns the new value to the left-handside |
+=, -=, *=, %= | Performs the given arithmetic operation with the expression of the right-handside to the left-handside |
/= | Performs the given division operation with the expression of the right-handside to the left-handside. The same restrictions as for regular division operations apply: It has to be either uint or int, a multiple of 2 and constant. |
&=, ^=, |= | Performs the given binary operation with the expression of the right-handside to the left-handside |
<<=, >>= | Performs the given shift operation with the expression of the right-handside to the left-handside. The same restriction as for the regular shift expression applies: The right-handside has to be either uint or int |
Except for the simple assignment the equivalent of the composed assignment a «op»= b operations is always: a=a «op» b
Never use a composed assignment on a non-register variable as it will lead to a combinatorial loop.
Infix and postfix notations such as a++ are not supported.
Variables with direction in can not be written
a=6; i.a+=b.c; c<<=2; x&=0xFF;
The if statement can be used to evaluate a boolean expression and perform some actions depending on the result. It always has the form: if(«booleanExpression») {«trueStatements»*} [else {«falseStatements»*}] The parenthesis { } are optional if the number of statements is exactly one. The else trunk is optional.
if(«Expression»!=0) {«trueStatements»*} [else {«falseStatements»*}]
if (a>6) c=5; if (a<=6) c=5; else c=7; if (b) { c=5; d=7; } if (a<=6) c=5; else if (a>7) c=8;
The operator ! can be used to negate a <booleanExpression>.
The switch statement can be used to avoid nesting several if
statements. It is also a useful tool to build state-machines. It always has the form:
switch(«caseExpression») {
[case«constant»:{«Statements»}]*
default:{«Statements»}
}
The default case is mandatory.
The parenthesis for the case statements are optional.
Each case must have the same bit width as the <caseExpression> and it has to be known constantly.
switch (a) { case 0b10: c=5; case TEST: c=1; case 0x82<<2: { c=7; } default: x=5; }
enum States={IDLE, WAITING, DOSOMETHING} register enum States state; switch (state) { case States.IDLE: state=States.WAITING; //You can let the Enum. away in switch //cases where switch operates on an enum case WAITING: state=DOSOMETHING; case States.DOSOMETHING: default: state=States.IDLE; }
The for loop iterates over a range and generates the structure that is described by it.
It does NOT evaluate the statements sequentially, that is, it does not describe a
state-machine. It always has the form:
for («iName»={«start»:«end»}) { «doStatements»* }.
for (I={0:WIDTH}){
a[I]=b{I};
z[I]=I*5+1;
}
The for loop:
for (I={1:3}){
a[I]=b{I-1};
}
is equivalent to the following code:
a[1]=b{1-1}; a[2]=b{2-1}; a[3]=b{3-1};
Expressions do return a value. There are the following kind of Expressions:
The general precedence order and the meaning of the operators is the same as for the language C and Java.
A variable can be simply referenced by naming it. The syntax is the following: [interfaceId[«array»]*]?id[«array»]*[{«bitAccess»}]?
i
This accesses the variable i
i{31:15}
This accesses the bits 31 down to 15 of i
i[5]{19}
This accesses the 6th element of the array i and the 20th bit.
i[5][1]{19}
This accesses the 6th array of the 2 dimensional array i. Within the second array the 2nd element is accessed and the 20th bit.
a.i
This accesses the variable i of the interface instance a.
a[3].i
This accesses the variable i of the 4th interface instance of a.
i{5,9:2}
This is a concatenation of the bits 5 and the bits 9 down to 2.
The bit 0 always designates the LSB
The expression i{5,9:2} is equivalent to i{5}#i{9:2}
Most well known arithmetic operations are supported. This includes + (add), -(subtract, arithmetic negate), * (multiply),% (modulo), and partially / (divide) as well as ** (exponentation).
Operation | 1st Operand | 2nd Operand |
+, -, * | uint, uint<?>, int, int<?> | uint, uint<?>, int, int<?> |
/, % | uint, uint<?>, int, int<?> | power of 2 uint (constant, parameter) |
** | 2 | uint (constant, parameter) |
All arithmetic is limited to int and uint of any width. Arithmetic with bit is only supported if explicitly cast to either int or uint.
For the operation a%b the signedness is determined by b.
Most well known binary operations are supported. This includes & (and), | (or), ^ (xor), >> (shift right), << (shift left) and ~ (invert).
The result of a bit operation is always of type of the left hand side, the right hand side is casted to match the left hand side. The width of the result is determined by the left-handside except if the left-handside is of type uint or int with no width. In that case the right-handside is taken.
As the arithmeticic operations do not accept bit vectors, it may become necessary to cast values to another type. This is done with the («targetType»)«expression».
bit<16> a=(uint) 0xFF; bit<16> b=(uint<4>) 0xFF; bit<16> c=(int<4>) 0xFF; bit<16> d=(int) 0xFF; bit<16> e=(int<16>)(-5); bit<16> f=(int<16>)(-40000); bit<16> g=(int<8>)(-5); bit<16> h=(int<8>)(-40000);
The cast to a bit vector is done implicitly. The resulting values are:
a == 0b0000_0000_1111_1111 b == 0b0000_0000_0000_1111 c == 0b0000_0000_0000_1111 d == 0b1111_1111_1111_1111 e == 0b1111_1111_1111_1011 (-5) f == 0b1110_0011_1100_0000 (-7232) g == 0b0000_0000_1111_1011 (251 if interpreted as int<16>) h == 0b0000_0000_1100_0000 (192 if interpreted as int<16>)
The operator for concatenating variables is: #.
a#b{31} c{31:15,0}#x[0]
The ternary operator allows to return a value based on a condition. It can replace small if constructs and make the code shorter.
In PSHDL there are currently three types of declarations:
The most important declaration.
«annotation*» «direction?» «register?» «type» «annotation*» id [«dimension»]* (= «defaultValue»)? (, «annotation*» id [«dimension»]* (= «defaultValue»)?)*;
Primitive types are the most used type. They can be parameterized with a width: «type»< width >. The width indicates the total amount of bits in that type.
registers can be configured. For this the following parameter can be provided:
register uint a; register(clock=$clk, clockEdge=Edge.RISING, reset=$rst, resetValue=0, resetSync=Sync.SYNC, resetType=Active.HIGH) uint a;
Enums are most useful for state-machines. You can declare an enum and use it as register.