Open documentation in new Window

PSHDL Online documentation

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.

Body

In the body of a PSHDL module you can find the following elements:

Each of which will be explained in the following sections

Statements

A Statement does not return a value. There are the following statements:

Assignments

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.

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;

If Statement

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 (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>.

Switch Statement

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; 
} 

For loop

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

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.

References

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}

Arithmetic operations

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.

Bit operations

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.

Cast Operation

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>)
			

Concatentation

The operator for concatenating variables is: #.

a#b{31} 
c{31:15,0}#x[0]

Ternary Operator

The ternary operator allows to return a value based on a condition. It can replace small if constructs and make the code shorter.

«booleanExpression»?«trueExpression»:«falseExpression»
It is a good idea to wrap the ternary operator in parentheses when used within an expression. It's precedence is quite low and might lead to unexpected results if used inproperly.

Declarations

In PSHDL there are currently three types of declarations:

Variable declarations

The most important declaration.

«annotation*» «direction?» «register?» «type» «annotation*» id [«dimension»]* (= «defaultValue»)? (, «annotation*» id [«dimension»]* (= «defaultValue»)?)*;

Primitives

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

registers can be configured. For this the following parameter can be provided:

These two are equivalent:
register uint a;
register(clock=$clk, clockEdge=Edge.RISING, 
	  reset=$rst, resetValue=0,
	  resetSync=Sync.SYNC, resetType=Active.HIGH) uint a;

Enum declarations

Enums are most useful for state-machines. You can declare an enum and use it as register.

enum «name» { «Enums*» }