diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6804a5c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/build/* +*.o +*.elf +*.a \ No newline at end of file diff --git a/README.md b/README.md index 7f7fc5bc..d4d499f6 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ Released on 1 Jul 2020 and maintained in the * [F. Tinsel API](#f-tinsel-api) * [G. HostLink API](#g-hostlink-api) * [H. Limitations on RV32IMF](#h-limitations-on-rv32imf) -* [I. Publications](#i-publications) +* [I. Testing](#i-testing) +* [J. Publications](#j-publications) ## 1. Overview @@ -1356,6 +1357,47 @@ to deal with highly non-uniform fanouts (i.e. where some vertices have tiny fanouts and others have huge fanouts; this could be alleviated by adding fanout as a vertex weight for METIS partitioning). +**Simulation**. There is a simple and partial functional simulation of the +POLite framework in [apps/util/POLiteSWSim](apps/util/POLiteSWSim). This +implements most of the common parts of the POLite `PGraph` and `PDevice` class, +along with just enough `HostLink` to give a run-time API. Assuming you +are using the default POLite makefile then it will be available for free. +So if you are currently doing: +``` +$ make all +$ (cd build && ./run ARGS) +``` +to run your application, you should be able to simulate using: +``` +$ make sim +$ (cd build && ./sim ARGS) +``` +All the makefile does is include the `POLiteSW/include` directory +ahead of the main `include/POLite` directory, so it overrides the +"real" header files. + +Note that the simulator is intended to uncover errors, and so it models +the fairly worst-case behaviour in terms of message delivery. In particular, it +inserts exponentially distributed random delays between sends and corresponding +receives, and so causes many messages to be delivered in a different order to +the sending order. + +There are two environment parameters that will affect the run-time +behaviour of the simulation: + +- `POLITE_SW_SIM_VERBOSITY` : Controls logging of simulation info to stderr. At 0 only + fatal errors are printed. For values larger than 0 increasing amounts of info are printed. + Default is 1, which prints progress information during simulation. Printing frequency + will slow down as the simulation takes longer. + +- `POLITE_SW_SIM_DELIVER_OUT_OF_ORDER` : Controls whether messages can be delayed and + delivered out of order in the simulation. By default this is "true", but if you set + it to "0" or "false" then you might find simulation a bit faster. Note that current + (0.8.3) hardware behaviour is somewhat in-between - messages can be delayed arbitrarily, + but for any (dst,src) pair the messages will be delivered in order. However, this was + not true in previous hardware, and may not be true in future hardware. Actually, + it is not true on current hardware for mixed unicast and multicast transmissions. + ## A. DE5-Net Synthesis Report The default Tinsel configuration on a single DE5-Net board contains: @@ -1925,7 +1967,35 @@ inerhit a number of limitations: See [Issue #54](https://github.com/POETSII/tinsel/issues/54) for more details. -## I. Publications +## I. Testing + +This repo contains (at least) the following automated self-test +facilities: + +- [Instruction tests](tests/) - These test the basic instruction + execution capabiities of the tinsel hardware. To run, do: + ``` + $ make -C tests + $ (cd tests && ./run) + ``` + These tests work on any machine with Tinsel hardware, and if a test + fails then something is probably quite seriously wrong with configuration + or the hardware has been mis-built. These tests (and indeed any + application) can also be run using the cycle-accurate hardware + simulator produced by the Bluespec compiler (`make sim` in the + `rtl` directory). However, this simulator is *very* slow (even + when `config.py` is modified to use fewer cores per board). + +- [POLiteSWSim tests](apps/POLite/util/POLiteSWSim) - These test + the POLiteSWSim framework (software simulation of POLite apps), + and can be run using: + ``` + $ apps/POLite/util/test_polite_sw_sim.sh + ``` + These tests should work on any machine, regardless of whether there + is Tinsel hardware installed. + +## J. Publications * *Tinsel: a manythread overlay for FPGA clusters*, FPL 2019 ([paper](https://www.repository.cam.ac.uk/handle/1810/294801)) diff --git a/apps/POLite/clocktree-async/Run.cpp b/apps/POLite/clocktree-async/Run.cpp index 02f76723..09033525 100644 --- a/apps/POLite/clocktree-async/Run.cpp +++ b/apps/POLite/clocktree-async/Run.cpp @@ -32,7 +32,8 @@ int main(int argc, char** argv) // Create POETS graph PGraph graph; graph.mapVerticesToDRAM = true; - graph.mapInEdgesToDRAM = true; + graph.mapInEdgeHeadersToDRAM=true; + graph.mapInEdgeRestToDRAM=true; graph.mapOutEdgesToDRAM = true; // Number of devices in tree diff --git a/apps/POLite/pressure-sync/Run.cpp b/apps/POLite/pressure-sync/Run.cpp index ee9bd698..526c0e85 100644 --- a/apps/POLite/pressure-sync/Run.cpp +++ b/apps/POLite/pressure-sync/Run.cpp @@ -6,14 +6,18 @@ #include #include -// Number of time steps -#define T 1000 - // Volume dimensions #define D 40 -int main() +int main(int argc, char *argv[]) { + int T=1000; + + if(argc>1){ + T=atoi(argv[1]); + fprintf(stderr, "Setting T=%u\n", T); + } + HostLink hostLink; PGraph graph; //graph.mapVerticesToDRAM = true; diff --git a/apps/POLite/util/POLiteSWSim/include/HostLink.h b/apps/POLite/util/POLiteSWSim/include/HostLink.h new file mode 100644 index 00000000..bbf0646f --- /dev/null +++ b/apps/POLite/util/POLiteSWSim/include/HostLink.h @@ -0,0 +1 @@ +#include "POLite.h" \ No newline at end of file diff --git a/apps/POLite/util/POLiteSWSim/include/POLite.h b/apps/POLite/util/POLiteSWSim/include/POLite.h new file mode 100644 index 00000000..19c60cb0 --- /dev/null +++ b/apps/POLite/util/POLiteSWSim/include/POLite.h @@ -0,0 +1,27 @@ +#ifndef POLiteSWSim_POLite_h +#define POLiteSWSim_POLite_h + +// Everything in here is in namespace POLiteSWSim +#include "POLite/PGraph.h" + +// We then explicitly being it out into the global namespace + +using POLiteSWSim::PGraph; +using POLiteSWSim::PDevice; +using POLiteSWSim::HostLink; +using POLiteSWSim::PMessage; + +using POLiteSWSim::None; +using POLiteSWSim::No; +using POLiteSWSim::HostPin; +using POLiteSWSim::PPin; +using POLiteSWSim::Pin; +using POLiteSWSim::PDeviceId; + +using namespace POLiteSWSim::config; + +// This is defined in tinsel-interface.h, and used in some apps. +// Defined empty here for compatibility, but would be nice to get rid of it +#define INLINE + +#endif \ No newline at end of file diff --git a/apps/POLite/util/POLiteSWSim/include/POLite/PGraph.h b/apps/POLite/util/POLiteSWSim/include/POLite/PGraph.h new file mode 100644 index 00000000..d02d9f44 --- /dev/null +++ b/apps/POLite/util/POLiteSWSim/include/POLite/PGraph.h @@ -0,0 +1,648 @@ +#ifndef POLiteSWSim_PGraph_h +#define POLiteSWSim_PGraph_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace POLiteSWSim { + +inline bool get_option_bool(const char *name, bool def) +{ + auto *p=getenv(name); + if(p){ + std::string s(p); + std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c);}); + if(s=="1" || s=="yes" || s=="true"){ + fprintf(stderr, "%s=%s, flag=true\n", name, s.c_str()); + return true; + } + if(s=="0" || s=="no" || s=="false"){ + fprintf(stderr, "%s=%s, flag=false\n", name, s.c_str()); + return false; + } + fprintf(stderr, "POLiteSWSim::PGraph::PGraph() : Error - didn't understand value for env var %s=%s\n", name, p); + exit(1); + } + return def; +} + +inline unsigned get_option_unsigned(const char *name, unsigned def) +{ + auto *p=getenv(name); + if(p){ + try{ + return std::stoul(p); + }catch(...){ + fprintf(stderr, "POLiteSWSim::PGraph::PGraph() : Error - didn't understand value for env var %s=%s\n", name, p); + exit(1); + } + } + return def; +} + +using PDeviceId = uint32_t; +typedef int32_t PinId; + +// This is a static limit on the number of pins per device +#ifndef POLITE_NUM_PINS +#define POLITE_NUM_PINS 1 +#endif + +// Pins +// No - means 'not ready to send' +// HostPin - means 'send to host' +// Pin(n) - means 'send to application pin number n' +struct PPin{ + uint8_t index; + + PPin(const PPin &) = default; + PPin &operator=(const PPin &o) = default; + + // The tag is to stop me accidentally constructing them, + // as I keep doing it... + explicit constexpr PPin(unsigned _index, bool _tag) + : index((uint8_t)_index) + {} + + constexpr bool operator==(PPin o) const { return index==o.index; } + constexpr bool operator!=(PPin o) const { return index!=o.index; } +}; +static_assert(sizeof(PPin)==1, "Expecting PPin to be 1 byte."); + +const constexpr PPin No = (PPin{0,true}); +const constexpr PPin HostPin = (PPin{1,true}); +constexpr PPin Pin(unsigned n) { return PPin{n+2,true}; } + +namespace config +{ + + const unsigned TinselLogWordsPerMsg = 4; + const unsigned TinselLogBytesPerMsg = 6; + const unsigned TinselLogBytesPerWord = 2; + const unsigned TinselLogBytesPerFlit = 4; + const unsigned TinselCoresPerFPU=32; + const unsigned TinselLogBytesPerDRAM=27; + const unsigned TinselMeshXBits=3; + const unsigned TinselMeshYBits=3; + const unsigned TinselBoxMeshXLen=4; + const unsigned TinselBoxMeshYLen=4; + +}; + +using namespace config; + +enum PlacerMethod +{ Default }; + +PlacerMethod parse_placer_method(const std::string &s) +{ return Default; } + +std::string placer_method_to_string(PlacerMethod p) +{ return "Default"; } + +// For template arguments that are not used +struct None {}; + +// Implementation detail +class PGraphBase +{ +public: + virtual ~PGraphBase() + {} + + virtual void sim_prepare() =0; + + virtual bool sim_step( + std::mt19937_64 &rng, + std::function send_cb + ) =0; +}; + +// HostLink parameters +struct HostLinkParams { + uint32_t numBoxesX; + uint32_t numBoxesY; + bool useExtraSendSlot; + + // Used to indicate when the hostlink moves through different phases, especially in construction + std::function on_phase; + + // Used to allow retries when connecting to the socket. When performing rapid sweeps, + // it is quite common for the first attempt in the next process to fail. + int max_connection_attempts = 1; +}; + +template +struct PMessage +{ + M payload; +}; + +class HostLink +{ +private: + std::mutex m_mutex; + std::condition_variable m_cond; + + std::deque> m_dev2host; + + std::atomic m_user_waiting; + std::atomic m_worker_interrupt; + std::atomic m_worker_running; + + unsigned verbosity; + + void worker_proc() + { + // To make debugging easier we hold the lock and occasionally release + // it, so that effectively only one thread is running + std::unique_lock lk(m_mutex); + + std::mt19937_64 rng; + rng.seed(time(0)); + + std::function send_cb=[&](void *p, size_t n) + { + // We don't put anything else in the PMessage. + static_assert(sizeof(PMessage) == sizeof(int)); + + // Need to expand up to full message size when sending to host. + // Tinsel guarantees that padding will be zero: `toPCIe.put(0);` + std::vector tmp(1<sim_prepare(); + + while(1){ + if(!m_graph->sim_step(rng, send_cb)){ + if(verbosity>=2){ + fprintf(stderr, "POLiteSWSim::HostLink : Info - Exiting device worker thread due to devices finishing.\n"); + } + break; + } + + if(m_worker_interrupt.load()){ + if(verbosity>=2){ + fprintf(stderr, "POLiteSWSim::HostLink : Info - Exiting worker thread due to interrupt (host finishing while devices still running, probably fine).\n"); + } + break; + } + if( (m_user_waiting.load() && !m_dev2host.empty()) ){ + m_cond.notify_all(); + m_cond.wait(lk); + } + } + + m_cond.notify_all(); + + m_worker_running.store(false); + } + + std::thread m_worker; +public: + HostLink() + : verbosity(POLiteSWSim::get_option_unsigned("POLITE_SW_SIM_VERBOSITY", 1)) + {} + + HostLink(const HostLinkParams) + {} + + ~HostLink() + { + m_worker_interrupt.store(true); + if(m_worker.joinable()){ + m_worker.join(); + } + } + + // Implementation detail + PGraphBase *m_graph=0; + + /* Used to work around destruction order. If the graph is destroyed before the + HostLink (which is common), we need to end the processing loop. */ + void detach_graph(PGraphBase *graph) + { + assert(graph==m_graph); + m_worker_interrupt.store(true); + std::unique_lock lk(m_mutex); + + m_cond.wait(lk, [&](){ return m_worker_running.load()==false; }); + } + + // No-op for SW + void boot(const char *code, const char *data) + {}; + + // Needs to create an _independent_ thread which runs in the + // background, then return + void go() + { + m_worker_interrupt.store(false); + m_worker_running.store(true); + m_worker=std::thread([=](){ worker_proc(); }); + } + + // Blocking receive of max size message + void recvMsg(void* msg, uint32_t numBytes) + { + if(numBytes > (1< lk(m_mutex); + + //fprintf(stderr, "Waitig for host message."); + + m_cond.wait(lk, [&](){ + return !m_dev2host.empty() || !m_worker_running.load(); + }); + + if(m_dev2host.empty()){ + fprintf(stderr, "POLiteSWSim::HostLink::recvMsg : Error - HostLink::recvMsg was called, but devices have all finished and there are no pending messages - app will block."); + exit(1); + } + + auto front=m_dev2host.front(); + m_dev2host.pop_front(); + + const int MaxMessageSize=1< +struct PDevice { + // Impementation + PPin _realReadyToSend = No; + + // State + S* s; + PPin* readyToSend; + uint32_t numVertices; + uint16_t time; + + // Handlers + void init(); + void send(volatile M* msg); + void recv(M* msg, E* edge); + bool step(); + bool finish(volatile M* msg); +}; + +template +struct PThread { + +}; + +template +class PGraph + : public PGraphBase // Implementation detail +{ +private: + HostLink *m_hostlink; + + std::mutex m_lock; + uint32_t m_maxFanOut=0; + uint32_t m_maxFanIn=0; + uint64_t m_edgeCount=0; + + unsigned verbosity=0; +public: + static constexpr bool is_simulation = true; + + std::function on_fatal_error; + std::function on_phase_hook; + std::function on_export_value; + std::function on_export_string; + + PlacerMethod placer_method=Default; + + PGraph() + { + deliver_out_of_order=POLiteSWSim::get_option_bool("POLITE_SW_SIM_DELIVER_OUT_OF_ORDER", true); + verbosity=POLiteSWSim::get_option_unsigned("POLITE_SW_SIM_VERBOSITY", 1); + } + + ~PGraph() + { + if(m_hostlink){ + m_hostlink->detach_graph(this); + m_hostlink=0; + } + } + + // This structure must be directly exposed to clients + struct PState + { + // Implementation stuff + // For outgoing we keep that 0==empty and 1==host + std::array>,POLITE_NUM_PINS+2> outgoing; + std::vector incoming; + unsigned fanOut=0; + + // User visible stuff + S state; + + std::mutex lock; + }; + + PDeviceId newDevice() + { + PDeviceId id=numDevices++; + devices.push_back(std::make_shared()); + memset(&devices.back()->state, 0, sizeof(S)); + return id; + } + + PDeviceId newDevices(unsigned n) + { + PDeviceId id=numDevices; + for(unsigned i=0; ioutgoing[pin]; + d.reserve(d.size()+n); + } + + + void addEdge(PDeviceId from, PinId pin, PDeviceId to) + { + addLabelledEdge({}, from, pin, to); + } + + void addLabelledEdgeImpl(E edge, PDeviceId from, PinId pin, PDeviceId to, bool lock_dst) + { + assert(pin dstDev=devices.at(to); + std::shared_ptr srcDev=devices.at(from); + unsigned key=dstDev->incoming.size(); + { + std::unique_lock lk(dstDev->lock, std::defer_lock); + if(lock_dst){ + lk.lock(); + } + dstDev->incoming.push_back(edge); + } + srcDev->outgoing[pin].push_back({to,key}); + srcDev->fanOut++; + + { + std::unique_lock lk(m_lock, std::defer_lock); + if(lock_dst){ + lk.lock(); + } + m_maxFanOut=std::max(m_maxFanOut, srcDev->fanOut); + m_maxFanIn=std::max(m_maxFanIn, dstDev->incoming.size()); + ++m_edgeCount; + } + } + + void addLabelledEdge(E edge, PDeviceId from, PinId pin, PDeviceId to) + { + addLabelledEdgeImpl(edge,from, pin, to, false); + } + + void addLabelledEdgeLockedDst(E edge, PDeviceId from, PinId pin, PDeviceId to) + { + addLabelledEdgeImpl(edge,from, pin, to, true); + } + + bool mapVerticesToDRAM=false; // Dummy flag + bool mapInEdgeHeadersToDRAM=false; // Dummy flag + bool mapInEdgeRestToDRAM=false; // Dummy flag + bool mapOutEdgesToDRAM=false; // Dummy flag + + // uint32_t i = graph.numDevices; + uint32_t numDevices = 0; + + // S &s = graph.devices[i]->state; + std::vector> devices; + + // Return total fanout of device, across all pins (I think) + uint32_t fanOut(PDeviceId id) + { + uint32_t acc=0; + for(const auto & c : devices.at(id)->outgoing ){ + acc += c.size(); + } + return acc; + } + + // Return total fanin of device, across all pins (I think) + uint32_t fanIn(PDeviceId id) + { + return devices.at(id)->incoming.size(); + } + + uint32_t getMaxFanOut() const + { return m_maxFanOut; } + + uint32_t getMaxFanIn() const + { return m_maxFanIn; } + + uint64_t getEdgeCount() const + { return m_edgeCount; } + + // No-op for sw + void map() + {} + + void write(HostLink *h) + { + assert(h->m_graph==0); + h->m_graph=this; + m_hostlink=h; + } + +private: + std::vector device_states; + + struct transit_msg + { + unsigned dst; + unsigned src; + unsigned key; + unsigned time; + M msg; + }; + + unsigned time_now=0; + unsigned max_time_skew=0; + uint64_t messages_sent=0; + uint64_t messages_received=0; + unsigned messages_in_flight_total=0; + uint64_t sum_messages_in_flight_since_last_print=0; + unsigned max_in_flight_ever=0; + unsigned next_print_time=10000; + unsigned time_since_print=0; + std::deque> messages_in_flight; + std::geometric_distribution<> msg_delay_distribution{0.1}; + + bool deliver_out_of_order; + + void post_message(std::mt19937_64 &rng, unsigned dst, unsigned src, unsigned key, const M &msg) + { + unsigned distance; + if(deliver_out_of_order){ + distance=msg_delay_distribution(rng); + }else{ + distance=1; + } + while(messages_in_flight.size() <= distance){ + messages_in_flight.push_back({}); + } + + messages_in_flight.at(distance).push_back({dst, src, key, time_now, msg}); + messages_in_flight_total ++; + messages_sent++; + } +public: + + virtual void sim_prepare() + { + device_states.resize(numDevices); + for(unsigned i=0; istate; + device_states[i].readyToSend = &device_states[i]._realReadyToSend; + device_states[i].numVertices=numDevices; // ? + device_states[i].time=0; // ? + + device_states[i].init(); + } + } + + virtual bool sim_step( + std::mt19937_64 &rng, + std::function send_cb + ){ + if(time_now >= next_print_time){ + if(verbosity >= 1){ + double avg_in_flight=sum_messages_in_flight_since_last_print / (double)time_since_print; + fprintf(stderr, "POLiteSWSim::PGraph::sim_step : Info - step=%u, sent=%llu, recv=%llu, in_flight:[now=%u, avg=%.1f, max=%u], max_skew=%u\n", + time_now, (unsigned long long)messages_sent, (unsigned long long)messages_received, messages_in_flight_total, avg_in_flight, max_in_flight_ever, max_time_skew); + } + next_print_time=(next_print_time*4)/3; + sum_messages_in_flight_since_last_print=0; + time_since_print=0; + } + max_in_flight_ever=std::max(max_in_flight_ever, messages_in_flight_total); + sum_messages_in_flight_since_last_print += messages_in_flight_total; + time_since_print++; + + bool idle=true; + for(unsigned i=0; ioutgoing.at(pin.index-2)){ + post_message(rng, e.first, i, e.second, msg); + } + } + } + idle=false; + } + } + + if(!messages_in_flight.empty()){ + const auto &now=messages_in_flight.front(); + for(const transit_msg &m : now){ + unsigned time_skew=time_now - m.time; + if(time_skew > max_time_skew){ + max_time_skew=time_skew; + } + device_states[m.dst].recv((M*)&m.msg, &devices[m.dst]->incoming[m.key]); + } + messages_in_flight_total -= now.size(); + messages_received += now.size(); + messages_in_flight.pop_front(); + idle=false; + } + + time_now++; + + if(!idle){ + return true; + } + + bool any_active=false; + for(unsigned i=0; i /dev/null && pwd )" +APPS_DIR=$(realpath $SCRIPT_DIR/..) + +COMPILE_APPS="asp-gals asp-sync asp-tiles-sync clocktree-async \ + hashmin-sync heat-cube-sync heat-gals heat-grid-sync \ + heat-sync \ + izhikevich-gals izhikevich-sync \ + pagerank-gals pagerank-sync \ + sssp-async sssp-sync \ + pressure-sync nhood-sync" + +echo "TAP version 13" + +TN=1 + +function record_ok { + echo "ok $TN $1" + TN=$((TN+1)) +} + +function record_not_ok { + if [[ "$2" != "" ]] ; then + echo "# Error in $1" + echo "# " + echo "$2" | while read LINE ; do + echo "# > $LINE" + done + fi + echo "not ok $TN $1" + TN=$((TN+1)) +} + +for APP in $COMPILE_APPS ; do + (cd $APPS_DIR/$APP && make clean) > /dev/null + + OUTPUT=$(cd $APPS_DIR/$APP && make sim 2>&1) + RES=$? + if [[ $RES -eq 0 ]] ; then + record_ok "Compile $APP with POLiteSWSim" + else + record_not_ok "Compile $APP POLiteSWSim" "$OUTPUT" + fi + + OUTPUT=$(cd $APPS_DIR/$APP && make sim-release 2>&1) + RES=$? + if [[ $RES -eq 0 ]] ; then + record_ok "Compile $APP with POLiteSWSim (release)" + else + record_not_ok "Compile $APP POLiteSWSim (release)" "$OUTPUT" + fi +done + +function test_run { + APP="$1" + shift + + NAME="Run $APP with args $@" + + if [[ ! -x $APPS_DIR/$APP/build/sim ]] ; then + record_not_ok "$NAME" "Sim executable not build by earlier test" + else + OUTPUT=$(cd $APPS_DIR/$APP/build && ./sim $@ 2>&1) + RES=$? + if [[ $RES -eq 0 ]] ; then + record_ok "$NAME" + else + record_not_ok "$NAME" "$OUTPUT" + fi + fi + + NAME="Run $APP with args $@ (release)" + + if [[ ! -x $APPS_DIR/$APP/build/sim-release ]] ; then + record_not_ok "$NAME" "Sim executable not build by earlier test" + else + OUTPUT=$(cd $APPS_DIR/$APP/build && ./sim-release $@ 2>&1) + RES=$? + if [[ $RES -eq 0 ]] ; then + record_ok "$NAME" + else + record_not_ok "$NAME" "$OUTPUT" + fi + fi +} + +test_run "clocktree-async" 5 5 +test_run "pressure-sync" 10 +test_run "nhood-sync" diff --git a/hostlink/Makefile b/hostlink/Makefile index 83dbf945..930d31d4 100644 --- a/hostlink/Makefile +++ b/hostlink/Makefile @@ -13,7 +13,7 @@ all: DebugLink.o HostLink.o MemFileReader.o jtag/UART.o pciestreamd \ sim/DebugLink.o sim/HostLink.o sim/MemFileReader.o sim/UART.o \ SocketUtils.o sim/SocketUtils.o udsock boardctrld \ sim/boardctrld fancheck \ - hostlink.a + hostlink.a sim/hostlink.a hostlink.a : DebugLink.o HostLink.o MemFileReader.o SocketUtils.o [ ! -f $@ ] || rm $@ diff --git a/include/POLite/PDevice.h b/include/POLite/PDevice.h index 758bb3ef..e42b3554 100644 --- a/include/POLite/PDevice.h +++ b/include/POLite/PDevice.h @@ -3,7 +3,6 @@ #define _PDEVICE_H_ #include -#include #include #ifdef TINSEL