-
Notifications
You must be signed in to change notification settings - Fork 50
"Hello, World!" in PipelineC. A Big Lie
For anyone just browsing around and thinking 'Hello World' must be the go-to first example for PipelineC - it is not - see digital logic basics instead as a start.
Hi Folks,
I want to share with you a "programming language" based on C called PipelineC. I wrote some code in PipelineC that mimics POSIX open,write,etc standard library functionality. So we can use that to write "Hello, World!" now. As the title says - lies are afoot so don't take terms too seriously. It may look like regular C code, but is actually not meant to 'run' on a CPU or GPU. Read on if you're curious and want to find out whats going on.
Here is a minimal "Hello, World!" in standard C.
int main()
{
int fd = open("/dev/stdout", O_RDWR);
write(fd, "Hello, World!\n", 14);
}
So how does "Hello, World!" work in PipelineC?
In PipelineC there are no actual standard C libraries because the "execution model" is not the same. In PipelineC the main() function(s) are "executing" or "being run" over and over again, as if in an outer infinite loop. Each "iteration" of a main() function must return (cannot exit, loop forever, make any arbitrary function calls that might not return, etc).
This might sound a little weird. But if you have ever coded up a control loop - perhaps for something like a microcontroller - you might be familiar with the idea of writing code inside a callback/interrupt routine/function that runs over and over again. Most of the time these routines are simple, just moving around bytes, think basic programming 101 C code.
Your "looping" main() must first open the file. System calls are broken into two parts: request and response. For instance, request to open path "/dev/stdout" and receive response with the file descriptor of the opened file.
Each "iteration" of the main function could output/return an arbitrary number of simultaneous system call request structs. Similarly each "iteration" could receive/input an arbitrary number of simultaneous system call response structs. However, in the below example we keep it simple, one system call "in flight" at a time. Make a request, wait for the response, move on. Inputs and return of the main function are how you receive and send responses and requests during each iteration.
Here is the full code for "Hello, World!". Below are the most important lines with a bit of pseudo code condensing done for clarity.
The state machine in this example does as follows to accomplish Hello World:
- Requests to open "/dev/stdout"
- Waits for the response from the "operating system" with the opened file descriptor
- Requests to write "Hello, World!" to opened file descriptor
- Waits for the response from the "operating system" with number of bytes written
PipelineC Hello World
// Includes omitted...
// Input and return type defs omitted...
// Each "iteration" of main() updates a state machine
// State machine to do the steps of
typedef enum state_t {
OPEN_REQ, // Ask to open the file (starts here)
OPEN_RESP, // Receive file descriptor
WRITE_REQ, // Ask to write to file descriptor
WRITE_RESP, // Receive how many bytes were written
DONE // Final state
} state_t;
state_t state;
fd_t fildes; // File descriptor for stdout
outputs_t main(inputs_t input)
{
outputs_t output; // Default all zeros
// State machine
// What gets done this iteration?
if(state==OPEN_REQ)
{
// Make valid request to open /dev/stdout
output.sys_open.req.path = "/dev/stdout";
output.sys_open.req.valid = 1;
// But the receiver of the request might not have been ready
// So keep outputting request until finally was ready
if(input.sys_open.req_ready)
{
// Then wait for response to request
state = OPEN_RESP;
}
}
else if(state==OPEN_RESP)
{
// Wait in this state ready for response
output.sys_open.resp_ready = 1;
// Until we get valid response
if(input.sys_open.resp.valid)
{
// Save file descriptor from response
fildes = input.sys_open.resp.fildes;
// Move onto writing to file by making write request
state = WRITE_REQ;
}
}
else if(state==WRITE_REQ)
{
// Make valid request to write "Hello, World!\n" to the file descriptor
output.sys_write.req.buf = "Hello, World!\n";
output.sys_write.req.nbyte = 14;
output.sys_write.req.fildes = fildes;
output.sys_write.req.valid = 1;
// But the receiver of the request might not have been ready
// So keep outputting request until finally was ready
if(input.sys_write.req_ready)
{
// Then wait for response to request
state = WRITE_RESP;
}
}
else if(state==WRITE_RESP)
{
// Wait in this state ready for response
output.sys_write.resp_ready = 1;
// Until we get valid response
if(input.sys_write.resp.valid)
{
// Would do something based on how many bytes were written
// input.sys_write.resp.nbyte
// But for now assume it went well, done.
state = DONE;
}
}
return output;
}
Its not simple. But I hope it's at least understandable for programmers familiar with C and POSIX libraries.
Whats the lie?
The operating system handling these "system calls" is actually the OS on an Amazon EC2 F1 FPGA instance. The FPGA attached to that instance is running the state machine described by the above PipelineC code (synthesizeable VHDL).
Tricked into being interested in FPGAs? Check out this example that reads from a file on the host disk, writes the data into an FPGA "block RAM file", and then writes that file back to the host disk. Want more info on the request and responses of this POSIX experiment in PipelineC? Maybe see the PipelineC GitHub for general info on the language.
I am looking for help on a few fronts:
- Improving this POSIX thing. Wouldn't it be great to have widely usable "operating system" (hah) for boiler plate FPGA code? PipelineC allows for "cross platform" code. I could get this running on a dev board I have at home too. Anyone with operating systems expertise would be great.
- Developing the PipelineC compiler, language, and debug tools. Right now I am trying to keep gcc compatibility to have something to test with. But if this was a real software language, with real software side support we could really go wild with hardware specific features and debug hooks included.
- General ideas on what to do next. Got a cool demo project idea? Lets do something cool together!
Very happy to answer questions. Thanks for your time folks.