An interface for a FPU compiled from VHDL
authorSam Moore <matches@ucc.asn.au>
Wed, 9 Apr 2014 15:41:58 +0000 (23:41 +0800)
committerSam Moore <matches@ucc.asn.au>
Wed, 9 Apr 2014 15:41:58 +0000 (23:41 +0800)
I have tested with the FPU from https://github.com/jop-devel/jop

The way this works is really awful...

1. Make (or steal from jop) a VHDL FPU
2. Make (or steal from jop) a testbench that uses stdio to read operands and ops
   - The jop one reads from a generated file, but it is trivial to change to "STD_INPUT" and remove all the assertion tests.
3. Compile testbench into an executable with ghdl
4. Call VFPU::Start which does the following:
   - Create a socketpair(2)
   - fork(2) and in the child dup(2) stdin/stdout to the socketpair
     - Redirect stderr to /dev/null because the testbench generates 10^3 warning messages
   - in the child, exec(3) the testbench FPU
5. Call VFPU::Exec passing the desired operands and opcodes
   - The operands and opcodes are written as hex strings over the socketpair
   - The child executing the FPU program does its thing
   - The result is read back as a hex string
6. When finished, call VFPU::Halt to kill(2) the FPU

I tried to get ghdl and gcc/g++ to make a static library to call ghdl_main instead
of exec'ing things. But that didn't work. The fork and socketpair would have still been
required for that approach anyway.

TODO:
  - Make the interface (not the FPU) deal with arbitrary size registers
  - Wrap a Real type around it so we can #define REAL_SIMULATED_FPU

I haven't put any of the VHDL in the git repo for obvious reasons.
If I actually start messing with it, it will probably get a seperate repo.

This may end up being a complete waste of time?
Although potentially being able to run an arbitrary FPU albeit in a horrible manner does seem cool.

src/Makefile
src/tests/vfpufloat.cpp [new file with mode: 0644]
src/vfpu.cpp [new file with mode: 0644]
src/vfpu.h [new file with mode: 0644]

index d7247ce..5d3ae00 100644 (file)
@@ -2,7 +2,7 @@
 ARCH := $(shell uname -i)
 CXX = g++ -std=gnu++0x -Wall -Werror -Wshadow -pedantic -g
 MAIN = main.o
-OBJ = log.o document.o view.o screen.o
+OBJ = log.o document.o view.o screen.o vfpu.o
 LIB_x86_64 = ../contrib/lib/libSDL2-2.0.so.0 -lGL
 LIB_i386 = ../contrib/lib32/libSDL2-2.0.so.0 -lGL
 LIB_unknown = $(LIB_x86_64)
diff --git a/src/tests/vfpufloat.cpp b/src/tests/vfpufloat.cpp
new file mode 100644 (file)
index 0000000..606ba5e
--- /dev/null
@@ -0,0 +1,14 @@
+#include "main.h"
+
+#include "vfpu.h"
+using namespace std;
+
+
+int main(int argc, char ** argv)
+{
+       VFPU::Start();
+       float result = VFPU::Exec(25,10, VFPU::SUB);
+       printf("%f\n", result);
+       VFPU::Halt();
+       return 0;
+}
diff --git a/src/vfpu.cpp b/src/vfpu.cpp
new file mode 100644 (file)
index 0000000..67f5249
--- /dev/null
@@ -0,0 +1,125 @@
+#include "vfpu.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <string.h>
+
+namespace VFPU
+{
+
+static const char g_fpu[] = "../vhdl/tb_fpu";
+
+static bool g_running = false;
+static int g_fpu_socket[2] = {-1,-1};
+static pid_t g_fpu_pid = -1;
+
+/**
+ * Starts the VFPU
+ * @returns 0 on success, errno of the failing function on failure
+ */
+int Start()
+{
+       assert(!g_running);
+       // create unix socket pair
+       
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, g_fpu_socket) != 0)
+               return errno;
+       
+
+       g_fpu_pid = fork();
+       if (g_fpu_pid < 0) // error check
+               return errno;
+
+
+       // Child branch
+       if (g_fpu_pid == 0)
+       {
+               
+               // Remap stdio to the socket
+               dup2(g_fpu_socket[0],fileno(stdin));
+               dup2(g_fpu_socket[0],fileno(stdout));
+               dup2(open("/dev/null", O_APPEND), fileno(stderr)); //LALALA I AM NOT LISTENING TO YOUR STUPID ERRORS GHDL
+               
+               // Unbuffer things; buffers are a pain
+               setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);
+
+               //fprintf(stderr, "Goodbye!\n");
+               execl(g_fpu, g_fpu,NULL);
+               fprintf(stderr, "Uh oh! %s\n", strerror(errno)); // We will never see this if something goes wrong... oh dear
+               exit(errno); // Child exits here.
+       }
+
+       // Parent branch
+       usleep(100);
+       g_running = true; // We are ready!
+       return 0;
+}
+
+/**
+ * Halt the VFPU
+ */
+int Halt()
+{
+       assert(g_running);
+       // Tell the child to stop running the VHDL simulation
+       if (close(g_fpu_socket[1]) != 0)
+               return errno;
+       usleep(1000);
+       if (kill(g_fpu_pid, SIGKILL) != 0)
+               return errno;
+       g_running = false;
+       return 0;
+}
+
+/**
+ * Tell the VFPU to execute an instruction, wait for it to finish, return the result
+ * TODO: Generalise for non 32bit Registers
+ */
+Register Exec(const Register & opa, const Register &  opb, Opcode op)
+{
+       assert(g_running);
+       
+       // Copy floats into 32 bits (casting will alter the representation) 
+       unsigned a; memcpy(&a, &opa, 8);
+       unsigned b; memcpy(&b, &opb, 8);
+
+       
+       char buffer[BUFSIZ];
+       int len = sprintf(buffer, "%08x\n%08x\n%03x\n",a, b, op);  // This is... truly awful... why am I doing this
+       //fprintf(stderr, "Writing:\n%s", buffer);
+
+       assert(len == 9+9+4); 
+       assert(write(g_fpu_socket[1], buffer, len) == len);
+       //fprintf(stderr, "Wrote!\n");
+
+       len = read(g_fpu_socket[1], buffer, sizeof(buffer));
+       assert(len == 9);
+       buffer[len] = '\0';
+       
+       
+       unsigned result = 0x00000000;
+       for (int i = 0; i < len/2; ++i)
+       {
+               unsigned byte2; // cos its two bytes
+               sscanf(buffer+2*i, "%02x", &byte2);
+               result |= (byte2 << 8*(len/2-i-1));
+       }
+       
+       //fprintf(stderr, "Buffer: %s\nResult: %08x\n", buffer, result);
+       
+       Register r;
+       memcpy(&r, &result, 8); // Amazing.
+       return r;
+}
+
+}
+
+
diff --git a/src/vfpu.h b/src/vfpu.h
new file mode 100644 (file)
index 0000000..da130a4
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef _VFPU_H
+#define _VFPU_H
+
+/**
+ * Implements a terrible and hacky interface to use a virtual FPU to do floating point operations
+ */
+
+namespace VFPU
+{
+       extern int Start(); // Starts the VFPU
+       extern int Halt(); // Halts the VFPU
+
+/**
+               -- 000 = add, 
+               -- 001 = substract, 
+               -- 010 = multiply, 
+               -- 011 = divide,
+               -- 100 = square root
+               -- 101 = unused
+               -- 110 = unused
+               -- 111 = unused
+ */
+       typedef enum {ADD=0x000, SUB=0x001, MULT=0x010, DIV=0x011, SQRT=0x100} Opcode;
+       typedef float Register;
+       
+       extern Register Exec(const Register & a, const Register & b, Opcode op);
+
+}
+
+#endif //_VFPU_H
+
+

UCC git Repository :: git.ucc.asn.au