Download Picoblaze C Compiler

Transcript
Picoblaze
C
Compiler
User's Manual 1.1
(2/Jul/2005)
pccomp alpha 1.7.x
Designed by Francesco Poderico
[email protected]
Introduction
Welcome to PCCOMP (the Picolaze's C Compiler).
If you are a FPGA designer and you are using picoblaze you will be happy to see a C compiler for Picoblaze.
The advantages of using a C compiler rather than assembler are obvious.
1. C is easy.
2. C is a portable language.
3. To debug and write a program in assembler may be very complicated.
The main disadvantage is that a program in C is not “short” as it would be if it was written in assembler
This compiler doesn't do any kind of code optimization. That is because at the moment I want to design
something that works correctly, even if is not efficient in terms of speed and code length. When the compiler is
completed maybe I will start to design the optimizer.
I hope you will enjoy using “Picoblaze C Compiler”.
Francesco Poderico.
Reporting a bug
If you are reading this document, you have probably accepted to help me in debugging this compiler. At the
moment I'm writing this manual for the compiler ver. alpha 1.x.x. I must say that I'm happy to see how things
are going, the compiler seems to generate good code even if is still not efficient.
If you find a bug please send an email to “[email protected]” with the subject: PCCOMP BUG
REPORT, also describe the bug, and if possible send me the code that has generated the bug. I'll reply when
the bug is fixed.
Installation and user interface of pccomp.exe
PCCOMP works on windows machines only, and has been tested on Windows XP and Windows me.
The installation of PCCOMP is very easy.
1. Create a directory called PCCOMP
2. unzip PCCOMP.zip in PCCOMP
Done!
To compile a source file, open a dos shell and go into the same directory where you have installed pccomp.
Type
pccomp [-c] [-v] [-s] file.c <enter>
where the option -c is to see your C code commented in the psm file genereted by pccomp.
If you use KCPSM2 then use the option -v.
If you use KCPSM3 and you want to use scratchpad ram to do stack operation use the option -s
If you use KCPSM do not use any options.
ex.
pccomp -c test1.c <enter>
pccomp creates a file called test1.psm and also displays any error(s) on the screen.
You can write the errors on an log file using the riderection operator.
For example if you want to write the errors in a file called error.log simple write:
(for example) pccomp -c -s test1.c >>error.log
If there are no errors open test1.psm and have a look.
With PCCOMP you can compile only one file at and it MUST contain the main() function.
(we'll see in future that that is not a limitation because you can include files with the #include directive.
PCCOMP generates assembler for KCPSM only!
If you are using the mediatronix IDE then you should translate from the “standard” assembler to the
mediatronix assembler from yourself.
THE PREPROCESSOR
PCCOMP has a simple but solid preprocessor. The way the preprocessor has been developed it is easy to
expand and more directives maybe added in the future.
The directive that it supports are:
#asm #endasm #ifdef #ifndef #else #endif
The meaning of these directive is the same as in ansi C.
#asm #endasm
The #asm directive must be used any time you need to write some code in assembler .
The assembler code should be included between the directive #asm and #endasm
for example if you need to do a jump from one part of your program to any other part you can write
#asm
JUMP_HERE:
#endasm
#asm
jump JUMP_HERE
#endasm
When you write some assembler code, pccomp doesn't do any kind of check beetween #asm and #endasm.
So, is your responsibility to check and be sure the assembler code is correct.
These directive are very useful when writing an interrupt routine. In fact the interrupt routine must be written in
assembler only and you should use registers s0,s1,s2 and s3.
#include
#include must be used to include external header file.
The header file in PCCOMP should be viewed a little bit different from ANSI C. In fact in the header
file you
can write some code. That should allow us (from my point of view) to write some driver directly in
the header file. For example if you need a driver that write to an UART, then you can create a header
file called UART.H and in it you can write your driver.
At moment I'm writing (2/jul/2005) I'm writing an UART.H driver to allow to write at an UART
attached to picoblaze.
In the PCCOMP distribution you should find some header that are:
spartan3.h, sqrt.h.
Have a look in it.
For example in spartan3.h you have 2 function to read and write a single char from the IO space of
picoblaze.
Example of use:
#include “spartan3.h”
Remember that PCCOMP doesn't do any kind of code optimization, so try to include ONLY the header file that
you really need or else you may run out of memory.
#define
#define works same as in ANSI C.
you can use this directive when you need conditional compilation or just to keep your code tidy.
One of the non ANSI features of PCCOMP is that you can't declare constant. So instead of that you can use
#define
Example of use:
#define one
#define CONST 10
#ifdef one
a= a + CONST ;
#endif
#ifndef one
a = a - CONST;
#endif
I recommend to use #define when you have a constant value that is used in more part of your code, so when
you need to change it you don't need to change all over the program, but you change the numeric value in one
place only.
#ifdef, #ifndef, #else #endif
The conditional compilation is an important feature of C, and it may be used with pcomp using the directive
#ifdef, #ifndef, #else and #endif
example of use:
#include “uart.h”
#define board1
// #define board2
void main(){
#ifdef board1
printUART(“board 1 ready!”);
#else
#ifdef board2
printUART(“board 2 ready!”);
#endif
#endif
}
The C Language implementation
The C language for picoblaze is a subset of the ANSI C of K&R. Picoblaze has only 256/1024 byte to allocate
it's program and 256 byte of data ram (picoblaze 3 only)
Probably picoblaze will be used to do simple things like i2c, to communicate with an UART, or simple
controller, etc.
The biggest issue with picoblaze is probably the program memory, it may run out easily. Especially if you write
all the program in C.
There are some tricks to save memory, one is to use global variable rather the local variable.
A good programmer prefer to use local variable as much as he cam, but here we are talking of program of
1kbyte of size.
Also using global variable will speed up the execution.
For example try that:
int a , b;
void main(){
a = b+1;
}
run pccomp the have a look at the generated assembler.
NAMEREG sf , XL
NAMEREG se , YL
NAMEREG sd , ZL
NAMEREG sc , XH
NAMEREG sa , ZH
NAMEREG sb , TMP
NAMEREG s9 , SH
NAMEREG s8 , SL
NAMEREG s7 , KH
NAMEREG s6 , KL
NAMEREG s5 , TMP2
CONSTANT
CONSTANT
CONSTANT
CONSTANT
LOAD
YL , fc
JUMP _main
_a_high
_a_low
_b_high
_b_low
,
,
,
,
ff
fe
fd
fc
_main:
INPUT
XL , _b_low
INPUT
XH,_b_high
SUB
YL , 01
OUTPUT
XH,(YL)
SUB
YL , 01
OUTPUT
XL,(YL)
LOAD ZL,01
LOAD ZH,00
INPUT
XL,(YL)
ADD
YL , 01
INPUT
XH,(YL)
ADD
YL , 01
ADD
XL , ZL
ADDCY XH , ZH
OUTPUT XL ,_a_low
OUTPUT XH ,_a_high
RETURN
now try to compile the following code and compare the psm files.
void main(){
int a , b;
a = b+1;
}
NAMEREG sf , XL
NAMEREG se , YL
NAMEREG sd , ZL
NAMEREG sc , XH
NAMEREG sa , ZH
NAMEREG sb , TMP
NAMEREG s9 , SH
NAMEREG s8 , SL
NAMEREG s7 , KH
NAMEREG s6 , KL
NAMEREG s5 , TMP2
_main:
SUB
YL , 04
LOAD
XL,
ADD
XL ,
SUB
YL , 01
OUTPUT
LOAD
XL,
ADD
XL ,
INPUT
ADD
XL , 01
INPUT
ADD
XL , 01
SUB
YL , 01
OUTPUT
SUB
YL , 01
OUTPUT
LOAD ZL,01
LOAD ZH,00
INPUT
ADD
YL , 01
INPUT
ADD
YL , 01
ADD
XL , ZL
ADDCY XH , ZH
LOAD
ZL , XL
INPUT
ADD
YL , 01
OUTPUT
ADD
XL , 01
OUTPUT
ADD
YL , 04
LOAD
XL , ZL
LOAD
XH , ZH
RETURN
LOAD
YL
02
XL,(YL)
YL
01
ZL , (XL)
ZH, (XL)
ZH,(YL)
ZL,(YL)
XL,(YL)
XH,(YL)
XL,(YL)
ZL,(XL)
ZH,(XL)
YL , 00
See? This file is much larger that the first one.
The reason of that is because when pccomp creates a local variable needs to allocate some space on the
data stack. In this case it needs to allocate 4 bytes to keep the variables a and b.
The access to a and b is slow because any time pccomp needs to see where is a or b he need to calculate.
When a variable is declared globally, the access is immediate because the compiler knows excactly where is
located and it doesn't need to recalculate any time.
When you write
a = b + 0;
pccomp is not clever enough to understand that what we want is “ a = b;”
and generate the code to do an addition between b and 0 and save the result in a!
You may have this problem when you write code like:
#define my_const 0
char a;
void main(){
a = a + my_const;}
Another example is a = a+2-(3*4) + 1;
I strongly do not recommend to write code like that!
pcomp will put 1 on the stack after 4 and 3 will calculate 3*4 and the result will go on the stack and added to 1
and put on the stack again and added to 2 and putted on the stack again and then will put a on the stack, and
calculate a+2 and saves the result in a!!!!
This is really not efficient.
So we learned that pccomp has not build inside any kind of optimizer.
In theory a compiler can be divided in two category stack and RISC optimized.Design a stack like compiler is
much easier than a RISC optimized.Because this kind of compiler don't care about the CPU architecture.
Even the debug is easier.PCCOMP is one of this... is a STACK compiler.. easy but working! (that is what I
want)
Read carefully this user manual and you should be able to use this compiler without problems. If you find a
bug, please send me an email.
I usually reply to everybody in less then 5 days unless I can't.
Type
PCCOMP supports at moment the following types:
char (-127 +128)
8 bit
int (-32727 +32768) 16 bit
unsigned int (0 .. +65535) 16 bit
unsigned char (0 .. 255) 8 bit
Some of you may complain that the long and the float types are not implemented, I may implemented them one day maybe. On the
other side if you need to use a float, maybe your program is quite complicated and picoblaze may not suite for that, for example imagine
how long it may take a multiplication between 2 float in software.
You can declare a variable as global and as local.
Global variable should be declared at the top of your program and not anywhere, this is also a good
practice.
The same rule is true for the local variables them should be declared at the top of the function.
We know that in C you can declare a variable (in practice) where you want. This is NOT the case of
PCCOMP.
from this point of view it looks a bit more like pascal
REMEMBER:With PCCOMP the variable must be declared only on the top of your function and/or on the top of your code.
I just said that PCCOMP has not an in build optimizer, so I'm giving you now some tips to “save space” .
Every time you declare a variable a compiler allocate same space in the memory.
When we declare a global variable the variable is there all the time, and the compiler knows exactly where is
located.
A different matter is for the local variables. A local variable exist only for the time that the function (where is
declared) exists (remember that even “main()” is a function).
So the compiler doesn't know were is located at the time of compilation, because could be anywhere.
For that reason a local variable must be calculate at the execution time. That cause the creation of code to
manage that.
if you want to write some code that is efficient I strongly suggest you to declare the variable globally.
Example of type declarations:
int i;
// integer 16 bits
char a,ii,aa[10];
// char +char + array of 10 elemnts of a char (12 bytes )
unsigned char q;
// unsigned char 8 bits
unsigned int w;
// unsigned integer 16bits
int *p;
// pointer to integer 8 bit
int pp[10];
// array of 10 integer (20 bytes)
Example of use
for(ii =0;ii<10;ii++) a[ii] = a[ii] +1; // OK!
for(i =0;i<10;i++) a[i] = a[i] +1; // WRONG
Q: Why the first for is correct and the second is wrong?
If you see above ii is declared a char, but i is declared as integer. Because Picoblaze 3 has a physical limit of
256 (the scrathcpad ram) and we don't want to use more memory that we need do we?Also if you think you
may need an array longer that 256 bytes.... maybe you need microblaze or the PPC.In that case Xilinx has
designed a cool tool called EDK (that I'm currently using at work) that is very cool nad
p = 0x00; // pointer at address 0x00
*p = 1; // write 1 at address 0x00
p = &pp; // p point at the first address of the array pp
p = &pp[0]; //WRONG PCCOMP doesn't allow that
Pointer and array
How we saw on the examples above we can declare pointer and array, with some limitations.
We can use only array with 1 dimension only.
The maximum array length is 256, and the index must be a char or an unsigned char.
This is not a real limitation because you can addresses up to 256 elements. Also the fact that you must use a
char as index for the array allow you to save some memory.
Pointers are very useful in C, on the next pages you can read how to use the pointers to do IO with the FPGA.
Also the pointer has some limitations;
p = &pp; // OK
p = &pp[0]; // WRONG is ANSI C is OK but with PCCOMP is illegal.
With C you can virtually do anything, but you with PCCOMP you have some limitation.
The pointer can point only to variables (global or local) and to array.
You can't have pointer to pointer.
For example:
char ** p; // This is illegal
When you point at an array be sure to use a syntax like: pointer = &array, (see example above) else the
compiler gives not working code.
I never wrote code like: p=&pp[i]; I think is horrible! So I don't think that is a big issue for everybody.
Bu just keep in mind that is not allowed to point at generic element of an array.
Casting between different types
When you have two or more variable of different type that must be added or divided (for example) between
them you should use the casting operator first. The casting is very useful and you can cast from:
1.
2.
3.
4.
char -> int
char -> unsigned int
unsigned char -> int
unsigned char -> unsigned int
The points 1, 3 and 4 doesn't need any precaution.
The point 2 it need some precaution, because the signed number (char can be consideted us a signed integer
on 8 buts) of -1 is “11111111” that is 255.
example :
char a;
unsigned int b;
void main(){
b = 0;
a = -1;
b = (unsigned int) a;
}
When we compile this code with PCCOMP and see the psm file we can notice that b is 255 now!
This is not a fault of the compiler, this is just bad coding. So, when you need casting, just be careful.
CONSTANT
CONSTANT
CONSTANT
LOAD
YL , fd
JUMP _main
_a
_b_high
_b_low
,
,
,
fe
fd
ff
;char a;
;unsigned int b;
;void main(){
_main:
;b = 0;
LOAD ZL,00
LOAD ZH,00
OUTPUT ZL,_b_low
OUTPUT ZH,_b_high
;a= -1;
LOAD ZL,ff
OUTPUT ZL,_a
;b = (unsigned int) a;
INPUT
ZL ,_a
OUTPUT ZL,_b_low
OUTPUT ZH,_b_high
;}
LOAD
XL , ZL
LOAD
XH , ZH
RETURN
You may fall in a similar problem in expressions like:
unsigned char a;
char b;
void main(){
b = -1;
a =1;
if a>b // .... 1 > -1 yes.. but the left opetar is an unsigned char and the right is a signed ???? is bad coding!
a = 0xaa;
else
a = 0xbb;
}
PCCOMP allows casting, but has not all the features of ANSI C.
When you write code like
i = 5;
the number 5 must be read from the compiler as integer if i is an integer, as a char if i is a char and so on.
The way PCCOMP works it use the last value putted on the stack to understand the type of the number.
That imply some attention.
Example this code is OK
char i;
int ss;
void main(){
ss = (int)(i - 5); // Ok!
}
i is a char and 5 is interpeted as char. Then the difference i-5 (still a char) is casted in integer
thats fine, but if you write instead:
char i;
int ss;
void main(){
ss = (int)i - 5; // WRONG
}
This code is wrong because i is a char, so the compiler bealive that we need a char, but in reality we have
converted i from char to integer. So pccomp generates code to do a subtraction between 2 char.
Unfortunatley PCCOMP doesn't gives any warning of that. So you need to be carefully.
When you are not sure try to be “safe” as possible. For example you can write:
char i;
int ss;
void main(){
ss =(int) i;
ss -=5; // OK;
}
IF statement
The syntax of the if statement is: if(<bool_expr>)<expr>[else]<expr>
ex.
if( i<=10) {
read = inchar(ff);
i++;
}
if (i <99)
ii = ii +1;
else
ii = ii -1;
WHILE statement
The syntax is: while(<bool_exp>) <expr>
ex.
a good example is the infinite loop.
while(1) {do dome ops} // this is wrong because 1 is not a boolean expressions
but ifyou write
while(1>0) {do some ops} // thi is OK because 1>0 is a boolean expression
If you need to do an infinite loop the most efficient way is NOT to use the while expression, but write your code
half in asm and half in C.
ex.
#asm
while1loop:
#endasm
// your code here
#asm
jump while1loop
#endasm
DO statement
The syntax of the do statement is: do<expr> while(bool_exp>)
example
do{
for(i=1;i<10;i++) c[i] = a[i]*b[j];
j++;
}while(j<10)
FOR statement
The syntax is: for(<expr>;<expr>;<expr>) <expr>
example;
for(i=1;i<10;i++) c[i] = a[i]*b[j];
SWITCH statement
The syntax is switch(<expr>) { case <const> : <expr>;[break] [case <const> : <expr>;[break] ]}
ex:
swtich(channel) {
case 'a' :
rr++;
break;
case 'b':
break;
default :
rr =0;
}
rr--;
INTERRUPTS
Like any other compiler pccomp supports interrupts. Picoblaze has only one external interrupt line that can be
enabled or disabled.
The command to enable the interrupt is IRQ_ON; // do not forgot at the end of line “;”
The command to disable the interrupt is IRQ_OFF;
The interrupt routine must be written in assembler, and you should use only the registers s0,s1,s2,s3.
The interrupt must be declared and defined, as any other functions.
The declaration of an interrupt routine is interrupt my_irq(void);
Example: we want to do the sum of a + b when when have an external interrupt request;
interrupt my_irq(void);
char a,b,c;
char had_an_irq;
void main(){
IRQ_ON;
while (1>0)
while(had_an_irq) {
IRQ_OFF;
had_an_irq = 0;
c=a+b;
IRQ_ON;
}
}
interrupt my_code(void){
#asm
load s0,1
store s0,_had_an_irq ; had_an_irq = 1
#endasm
}
To Remember
Picoblaze has two way to end and interrupt routine:
RETURNI ENABLE
RETURNI DISABLE
For default the version alpha 1.x.x has RETURNI DISABLE.
.
FUNCTIONS
the parameter of function can not be pointer or array.
Any function must be declared and defined.
Example:
char sum (char a, char b);
char a,b,apb;
char sum(char a, char b){
return a +b;
}
void main()
{
.
.
apb = sum(a,b);
}
Examples
I've included with the compiler some tests examples.
I was asked to generates more examples, and I'm doing so.
I will appreciate if you could send me any C code example that could be interesting to add in this
sections. For example if you have done some I2C controller or an LCD driver, etc.
SPARTAN3.h
In spartan3.h you will find 2 routines useful when you need to do IO and you are using picoblaze 3.
The library has 2 functions:
1 char inchar(char addr)
2 void outchar(char addr, char data)
The function inchar is used to read a char from the IO space at address addr.
For example:
char xx;
// some code
xx = inchar(0x01);// here we are reading at the IO space 1
when we need to write a char in a specific location we may write code like:
outchar(0x01, xx);
In this case we wrote xx at the IO 0x01.
It may be interesting to have a look at the spartan3.h library, and I copied here for your interest.
void outchar(char addr, char data); // write a character to the io space
char inchar( char addr);
// read a character from the io space
//
//
//
//
/////////////////////////////////////
// READ FUNCTION
char inchar(char addr){
char x;
#asm
LOAD TMP , YL
ADD TMP , 01
FETCH TMP, (TMP) ; TMP = addr
INPUT TMP,(TMP) ; TMP = datain
STORE TMP,(YL) ; save in x
#endasm
return x;
}
//
//
//
//
///////////////////////////////////////
// WRITE FUNCTION
void outchar(char addr , char data){
#asm
FETCH TMP,(YL) ; TMP = data
LOAD TMP2,YL
ADD TMP2 , 01
FETCH TMP2,(TMP2) ; TMP2 = addr
OUTPUT TMP,(TMP2)
#endasm
//
//
// END LIBRARY SPARTAN3.H
/////////////////////////////////////
}