From 8c411cf24ed0eb889191aaeafd8fa1e69081df42 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Nov 2013 17:47:40 +0100 Subject: [PATCH] dol: initial dol commit This is the original, unchanged DOL source code as provided by ETHZ. It is meant to be built with Ant. --- dol/.classpath | 9 + dol/.project | 17 + dol/build.xml | 183 ++ dol/examples/arch/cell.xml | 29 + dol/examples/arch/mparm.xml | 31 + dol/examples/arch/rdt1.xml | 139 + dol/examples/arch/rdt8.xml | 2059 +++++++++++++ dol/examples/example1/example1.xml | 73 + dol/examples/example1/map_1.xml | 17 + dol/examples/example1/map_3.xml | 17 + dol/examples/example1/map_mparm.xml | 40 + dol/examples/example1/src/arraygen.c | 42 + dol/examples/example1/src/arraygen.h | 18 + dol/examples/example1/src/consumer.c | 26 + dol/examples/example1/src/consumer.h | 18 + dol/examples/example1/src/generator.c | 27 + dol/examples/example1/src/generator.h | 17 + dol/examples/example1/src/global.h | 9 + dol/examples/example1/src/printarray.c | 25 + dol/examples/example1/src/printarray.h | 17 + dol/examples/example1/src/quicksort.c | 120 + dol/examples/example1/src/quicksort.h | 21 + dol/examples/example1/src/square.c | 27 + dol/examples/example1/src/square.h | 18 + dol/examples/example2/example2.xml | 82 + dol/examples/example2/src/consumer.c | 26 + dol/examples/example2/src/consumer.h | 18 + dol/examples/example2/src/generator.c | 27 + dol/examples/example2/src/generator.h | 17 + dol/examples/example2/src/global.h | 6 + dol/examples/example2/src/square.c | 27 + dol/examples/example2/src/square.h | 18 + dol/examples/example3/example3.xml | 186 ++ dol/examples/example3/src/forward.c | 19 + dol/examples/example3/src/forward.h | 18 + .../example3/src/horizontal_consumer.c | 16 + .../example3/src/horizontal_consumer.h | 15 + .../example3/src/horizontal_generator.c | 24 + .../example3/src/horizontal_generator.h | 17 + dol/examples/example3/src/vertical_consumer.c | 15 + dol/examples/example3/src/vertical_consumer.h | 15 + .../example3/src/vertical_generator.c | 23 + .../example3/src/vertical_generator.h | 17 + dol/examples/example4/example4.xml | 344 +++ dol/examples/example4/src/addmult.c | 28 + dol/examples/example4/src/addmult.h | 20 + dol/examples/example4/src/constants.h | 20 + dol/examples/example4/src/consumer.c | 29 + dol/examples/example4/src/consumer.h | 18 + dol/examples/example4/src/generator.c | 65 + dol/examples/example4/src/generator.h | 22 + dol/examples/example5/example5.xml | 305 ++ dol/examples/example5/fft_script.m | 146 + dol/examples/example5/src/consumer.c | 29 + dol/examples/example5/src/consumer.h | 18 + dol/examples/example5/src/fft2.c | 75 + dol/examples/example5/src/fft2.h | 21 + dol/examples/example5/src/generator.c | 47 + dol/examples/example5/src/generator.h | 19 + dol/examples/example5/src/global.h | 13 + dol/examples/example6/example6.xml | 94 + dol/examples/example6/src/consumer.c | 23 + dol/examples/example6/src/consumer.h | 15 + dol/examples/example6/src/filter.c | 32 + dol/examples/example6/src/filter.h | 20 + dol/examples/example6/src/producer.c | 52 + dol/examples/example6/src/producer.h | 17 + dol/examples/example7/example7.xml | 170 ++ dol/examples/example7/filter_script.m | 15 + dol/examples/example7/src/consumer.c | 23 + dol/examples/example7/src/consumer.h | 16 + dol/examples/example7/src/filter.c | 83 + dol/examples/example7/src/filter.h | 23 + dol/examples/example7/src/global.h | 7 + dol/examples/example7/src/producer.c | 50 + dol/examples/example7/src/producer.h | 19 + dol/examples/examplecell/cell.xml | 29 + dol/examples/examplecell/examplecell.xml | 83 + dol/examples/examplecell/mapping.xml | 71 + dol/examples/examplecell/src/consumer.c | 23 + dol/examples/examplecell/src/consumer.h | 19 + dol/examples/examplecell/src/generator.c | 23 + dol/examples/examplecell/src/generator.h | 19 + dol/examples/examplecell/src/global.h | 6 + dol/examples/examplecell/src/square.c | 24 + dol/examples/examplecell/src/square.h | 20 + .../exampleproducerconsumer.xml | 67 + .../exampleproducerconsumer/src/consumer.c | 21 + .../exampleproducerconsumer/src/consumer.h | 16 + .../exampleproducerconsumer/src/producer.c | 37 + .../exampleproducerconsumer/src/producer.h | 18 + .../examplesingleprocess.xml | 16 + dol/examples/examplesingleprocess/src/task.c | 21 + dol/examples/examplesingleprocess/src/task.h | 15 + .../examplewindowedfifo.xml | 74 + .../examplewindowedfifo/src/consumer.c | 27 + .../examplewindowedfifo/src/consumer.h | 18 + .../examplewindowedfifo/src/generator.c | 29 + .../examplewindowedfifo/src/generator.h | 17 + dol/examples/examplewindowedfifo/src/global.h | 6 + dol/examples/examplewindowedfifo/src/square.c | 29 + dol/examples/examplewindowedfifo/src/square.h | 18 + dol/examples/runexample.xml | 285 ++ dol/examples/runprofiler.xml | 45 + dol/examples/schema/architecture.xsd | 130 + dol/examples/schema/createschemastex | 91 + dol/examples/schema/generics.xsd | 51 + .../schema/internal/architecture_internal.xsd | 4 + .../schema/internal/mapping_internal.xsd | 4 + .../internal/processnetwork_internal.xsd | 26 + dol/examples/schema/mapping.xsd | 96 + dol/examples/schema/processnetwork.xsd | 142 + dol/jars/jdom.jar | Bin 0 -> 153253 bytes dol/jars/xercesImpl.jar | Bin 0 -> 1207073 bytes dol/src/MANIFEST.MF | 4 + dol/src/docs/doxygen/doxygen.cfg | 1078 +++++++ dol/src/docs/doxygen/footer.html | 3 + dol/src/docs/doxygen/header.html | 1 + dol/src/dol.properties | 15 + dol/src/dol/check/SanityCheck.java | 419 +++ dol/src/dol/check/package.html | 20 + dol/src/dol/datamodel/XmlTag.java | 76 + .../architecture/ArchiConnection.java | 80 + .../datamodel/architecture/ArchiResource.java | 254 ++ .../datamodel/architecture/Architecture.java | 400 +++ .../datamodel/architecture/Configuration.java | 113 + .../dol/datamodel/architecture/HWChannel.java | 128 + .../dol/datamodel/architecture/Memory.java | 170 ++ dol/src/dol/datamodel/architecture/Node.java | 211 ++ dol/src/dol/datamodel/architecture/Path.java | 118 + .../dol/datamodel/architecture/PortNode.java | 253 ++ .../dol/datamodel/architecture/Processor.java | 135 + .../dol/datamodel/architecture/ReadPath.java | 115 + .../dol/datamodel/architecture/Variable.java | 109 + .../dol/datamodel/architecture/WritePath.java | 113 + .../dol/datamodel/architecture/package.html | 20 + dol/src/dol/datamodel/mapping/Binding.java | 87 + .../mapping/CommunicationBinding.java | 89 + .../datamodel/mapping/ComputationBinding.java | 75 + .../dol/datamodel/mapping/Configuration.java | 75 + .../dol/datamodel/mapping/MapResource.java | 116 + dol/src/dol/datamodel/mapping/Mapping.java | 274 ++ dol/src/dol/datamodel/mapping/Schedule.java | 160 + .../dol/datamodel/mapping/ScheduleEntry.java | 113 + .../datamodel/mapping/SchedulingPolicy.java | 43 + dol/src/dol/datamodel/mapping/Variable.java | 108 + dol/src/dol/datamodel/mapping/package.html | 20 + dol/src/dol/datamodel/package.html | 20 + dol/src/dol/datamodel/pn/Channel.java | 129 + dol/src/dol/datamodel/pn/Configuration.java | 112 + dol/src/dol/datamodel/pn/Connection.java | 130 + dol/src/dol/datamodel/pn/Port.java | 189 ++ dol/src/dol/datamodel/pn/Process.java | 150 + dol/src/dol/datamodel/pn/ProcessNetwork.java | 189 ++ .../datamodel/pn/ProfilingConfiguration.java | 34 + dol/src/dol/datamodel/pn/Resource.java | 269 ++ dol/src/dol/datamodel/pn/Schedulable.java | 10 + dol/src/dol/datamodel/pn/SourceCode.java | 131 + dol/src/dol/datamodel/pn/Variable.java | 109 + dol/src/dol/datamodel/pn/package.html | 20 + .../dol/helper/flattener/ArchFlattener.java | 281 ++ dol/src/dol/helper/flattener/BugCatcher.java | 26 + .../helper/flattener/DomDocumentParser.java | 41 + .../dol/helper/flattener/FlattenerHelper.java | 314 ++ .../helper/flattener/MappingFlattener.java | 156 + dol/src/dol/helper/flattener/PNFlattener.java | 184 ++ .../helper/flattener/SaxDocumentParser.java | 107 + .../dol/helper/flattener/XMLFlattener.java | 71 + dol/src/dol/helper/flattener/package.html | 20 + .../dol/helper/profiler/ChannelProfile.java | 221 ++ dol/src/dol/helper/profiler/Constants.java | 13 + .../helper/profiler/PNProfileSummarizer.java | 105 + dol/src/dol/helper/profiler/PortProfile.java | 95 + .../dol/helper/profiler/ProcessProfile.java | 97 + .../dol/helper/profiler/ProfileParser.java | 273 ++ dol/src/dol/helper/profiler/Profiler.java | 212 ++ dol/src/dol/helper/profiler/Range.java | 176 ++ .../helper/profiler/VSPLogFileProfiler.java | 73 + .../helper/profiler/WorkloadAnnotator.java | 102 + dol/src/dol/helper/profiler/package.html | 20 + .../dol/helper/validator/XMLValidator.java | 77 + dol/src/dol/helper/validator/package.html | 20 + dol/src/dol/main/Main.java | 263 ++ dol/src/dol/main/Options.java | 207 ++ dol/src/dol/main/UserInterface.java | 771 +++++ dol/src/dol/main/package.html | 20 + dol/src/dol/parser/xml/XmlErrorHandler.java | 45 + dol/src/dol/parser/xml/XmlParser.java | 84 + .../xml/archischema/ArchiXmlParser.java | 187 ++ .../dol/parser/xml/archischema/Xml2Archi.java | 630 ++++ .../dol/parser/xml/archischema/package.html | 21 + .../parser/xml/mapschema/MapXmlParser.java | 211 ++ dol/src/dol/parser/xml/mapschema/Xml2Map.java | 381 +++ dol/src/dol/parser/xml/mapschema/package.html | 21 + dol/src/dol/parser/xml/package.html | 20 + .../dol/parser/xml/pnschema/PNXmlParser.java | 158 + dol/src/dol/parser/xml/pnschema/Xml2PN.java | 457 +++ dol/src/dol/parser/xml/pnschema/package.html | 21 + dol/src/dol/util/ApplicationGenerator.java | 914 ++++++ dol/src/dol/util/CheckXMLs.java | 45 + dol/src/dol/util/CodePrintStream.java | 115 + dol/src/dol/util/CodePrintString.java | 129 + dol/src/dol/util/Copier.java | 155 + dol/src/dol/util/JarCopier.java | 114 + dol/src/dol/util/SchemaLocation.java | 137 + dol/src/dol/util/Sed.java | 133 + dol/src/dol/util/package.html | 20 + dol/src/dol/visitor/ArchiVisitor.java | 47 + dol/src/dol/visitor/MapVisitor.java | 41 + dol/src/dol/visitor/PNVisitor.java | 44 + .../PipeAndFilterMakefileVisitor.java | 63 + .../PipeAndFilterModuleVisitor.java | 311 ++ .../PipeAndFilterProcessVisitor.java | 60 + .../PipeAndFilter/PipeAndFilterVisitor.java | 96 + .../visitor/PipeAndFilter/lib/Condition.cpp | 31 + .../dol/visitor/PipeAndFilter/lib/Condition.h | 21 + .../dol/visitor/PipeAndFilter/lib/Event.cpp | 73 + dol/src/dol/visitor/PipeAndFilter/lib/Event.h | 30 + .../dol/visitor/PipeAndFilter/lib/Fifo.cpp | 229 ++ dol/src/dol/visitor/PipeAndFilter/lib/Fifo.h | 31 + .../dol/visitor/PipeAndFilter/lib/Mutex.cpp | 48 + dol/src/dol/visitor/PipeAndFilter/lib/Mutex.h | 22 + .../PipeAndFilter/lib/ProcessWrapper.cpp | 128 + .../PipeAndFilter/lib/ProcessWrapper.h | 55 + .../visitor/PipeAndFilter/lib/Scheduler.cpp | 185 ++ .../dol/visitor/PipeAndFilter/lib/Scheduler.h | 35 + .../PipeAndFilter/lib/WindowedFifo.cpp | 304 ++ .../visitor/PipeAndFilter/lib/WindowedFifo.h | 41 + dol/src/dol/visitor/PipeAndFilter/lib/dol.h | 39 + .../visitor/PipeAndFilter/lib/dolSupport.cpp | 133 + .../visitor/PipeAndFilter/lib/dolSupport.h | 151 + .../dol/visitor/PipeAndFilter/package.html | 20 + dol/src/dol/visitor/Visitor.java | 9 + .../dol/visitor/cbe/CbeBuildFileVisitor.java | 109 + .../dol/visitor/cbe/CbeConstantVisitor.java | 102 + .../dol/visitor/cbe/CbeMakefileVisitor.java | 151 + dol/src/dol/visitor/cbe/CbeModuleVisitor.java | 417 +++ .../dol/visitor/cbe/CbeProcessVisitor.java | 340 +++ dol/src/dol/visitor/cbe/CbeVisitor.java | 183 ++ dol/src/dol/visitor/cbe/lib/ProcessFifo.h | 219 ++ dol/src/dol/visitor/cbe/lib/ProcessWrapper.h | 248 ++ .../dol/visitor/cbe/lib/ProcessWrapperHelp.h | 100 + .../dol/visitor/cbe/lib/ProcessWrapperPPE.c | 150 + dol/src/dol/visitor/cbe/lib/common.h | 59 + dol/src/dol/visitor/cbe/lib/common_ppu.h | 41 + dol/src/dol/visitor/cbe/lib/dol.h | 28 + dol/src/dol/visitor/cbe/lib/estimation.h | 50 + dol/src/dol/visitor/cbe/lib/free_align.h | 65 + dol/src/dol/visitor/cbe/lib/malloc_align.h | 105 + dol/src/dol/visitor/cbe/lib/ppu_main.h | 76 + .../dol/visitor/cbe/lib/ppu_main_workloop.h | 235 ++ .../template/ppu_process_wrapper_template.c | 68 + .../template/spu_process_wrapper_template.c | 150 + .../visitor/cell/CellBuildFileVisitor.java | 110 + .../dol/visitor/cell/CellConstantVisitor.java | 128 + .../dol/visitor/cell/CellMakefileVisitor.java | 130 + dol/src/dol/visitor/cell/CellMapping.java | 324 ++ .../dol/visitor/cell/CellModuleVisitor.java | 386 +++ dol/src/dol/visitor/cell/CellPPEVisitor.java | 421 +++ .../dol/visitor/cell/CellProcessVisitor.java | 652 +++++ dol/src/dol/visitor/cell/CellSPEVisitor.java | 416 +++ dol/src/dol/visitor/cell/CellVisitor.java | 179 ++ dol/src/dol/visitor/cell/lib/common.h | 91 + dol/src/dol/visitor/cell/lib/dol.h | 34 + dol/src/dol/visitor/cell/lib/estimation.h | 50 + dol/src/dol/visitor/cell/lib/free_align.h | 65 + dol/src/dol/visitor/cell/lib/malloc_align.h | 105 + .../cell/lib/ppu/FastCommunication.cpp | 617 ++++ .../visitor/cell/lib/ppu/FastCommunication.h | 118 + dol/src/dol/visitor/cell/lib/ppu/Fifo.cpp | 142 + dol/src/dol/visitor/cell/lib/ppu/Fifo.h | 45 + .../visitor/cell/lib/ppu/ProcessWrapper.cpp | 66 + .../dol/visitor/cell/lib/ppu/ProcessWrapper.h | 32 + .../dol/visitor/cell/lib/ppu/WindowedFifo.cpp | 223 ++ .../dol/visitor/cell/lib/ppu/WindowedFifo.h | 61 + dol/src/dol/visitor/cell/lib/ppu/common.cpp | 28 + dol/src/dol/visitor/cell/lib/ppu/common_ppu.h | 32 + .../dol/visitor/cell/lib/ppu/dolSupport.cpp | 98 + dol/src/dol/visitor/cell/lib/ppu/dolSupport.h | 78 + dol/src/dol/visitor/cell/lib/ppu_main.h | 43 + .../dol/visitor/cell/lib/pt/lc-addrlabels.h | 85 + dol/src/dol/visitor/cell/lib/pt/lc-switch.h | 76 + dol/src/dol/visitor/cell/lib/pt/lc.h | 132 + dol/src/dol/visitor/cell/lib/pt/pt-sem.h | 228 ++ dol/src/dol/visitor/cell/lib/pt/pt.h | 323 ++ .../cell/lib/spu/FastCommunication.cpp | 707 +++++ .../visitor/cell/lib/spu/FastCommunication.h | 119 + dol/src/dol/visitor/cell/lib/spu/Fifo.cpp | 182 ++ dol/src/dol/visitor/cell/lib/spu/Fifo.h | 47 + .../dol/visitor/cell/lib/spu/WindowedFifo.cpp | 277 ++ .../dol/visitor/cell/lib/spu/WindowedFifo.h | 60 + dol/src/dol/visitor/cell/lib/spu/common.cpp | 29 + .../dol/visitor/cell/lib/spu/dolSupport.cpp | 98 + dol/src/dol/visitor/cell/lib/spu/dolSupport.h | 88 + .../dol/visitor/cell/lib/spu/proc_wrapper.cpp | 92 + .../dol/visitor/cell/lib/spu/proc_wrapper.h | 55 + dol/src/dol/visitor/cell/lib/spu_mfcio_ext.h | 173 ++ dol/src/dol/visitor/cell/lib/spu_os.h | 35 + .../template/spu_process_wrapper_template.cpp | 66 + .../template/spu_process_wrapper_template.h | 56 + dol/src/dol/visitor/dot/ArchDotVisitor.java | 250 ++ dol/src/dol/visitor/dot/MapDotVisitor.java | 80 + dol/src/dol/visitor/dot/PNDotVisitor.java | 163 ++ dol/src/dol/visitor/dot/package.html | 21 + .../dol/visitor/hds/HdsMakefileVisitor.java | 93 + dol/src/dol/visitor/hds/HdsModuleVisitor.java | 347 +++ .../dol/visitor/hds/HdsProcessVisitor.java | 232 ++ dol/src/dol/visitor/hds/HdsVisitor.java | 100 + dol/src/dol/visitor/hds/lib/Fifo.cpp | 254 ++ dol/src/dol/visitor/hds/lib/Fifo.h | 29 + .../hds/lib/Performance_Extraction.cpp | 447 +++ .../visitor/hds/lib/Performance_Extraction.h | 67 + .../dol/visitor/hds/lib/ProcessWrapper.cpp | 157 + dol/src/dol/visitor/hds/lib/ProcessWrapper.h | 58 + dol/src/dol/visitor/hds/lib/WindowedFifo.cpp | 288 ++ dol/src/dol/visitor/hds/lib/WindowedFifo.h | 40 + dol/src/dol/visitor/hds/lib/dol.h | 39 + dol/src/dol/visitor/hds/lib/dolSupport.cpp | 149 + dol/src/dol/visitor/hds/lib/dolSupport.h | 148 + dol/src/dol/visitor/hds/lib/dol_sched_if.h | 43 + .../dol/visitor/hds/lib/functional_trace.cpp | 295 ++ .../dol/visitor/hds/lib/functional_trace.h | 57 + dol/src/dol/visitor/hds/lib/trace.h | 38 + dol/src/dol/visitor/hds/lib/xmlParser.cpp | 2594 +++++++++++++++++ dol/src/dol/visitor/hds/lib/xmlParser.h | 529 ++++ .../dol/visitor/hdsd/HdsdMakefileVisitor.java | 134 + .../visitor/hdsd/HdsdModuleArchVisitor.java | 491 ++++ .../dol/visitor/hdsd/HdsdModulePNVisitor.java | 113 + .../dol/visitor/hdsd/HdsdModuleVisitor.java | 38 + .../dol/visitor/hdsd/HdsdProcessVisitor.java | 364 +++ .../dol/visitor/hdsd/HdsdScriptVisitor.java | 376 +++ dol/src/dol/visitor/hdsd/HdsdVisitor.java | 113 + dol/src/dol/visitor/hdsd/lib/dol.c | 42 + dol/src/dol/visitor/hdsd/lib/dol.h | 141 + dol/src/dol/visitor/hdsd/lib/dol_sched_if.h | 43 + dol/src/dol/visitor/hdsd/lib/simple_fifo.h | 186 ++ .../dol/visitor/hdsd/scd/fsm/scd_cont_fsm.cpp | 30 + .../dol/visitor/hdsd/scd/fsm/scd_cont_fsm.h | 54 + .../visitor/hdsd/scd/fsm/scd_cont_fsm_if.h | 80 + .../dol/visitor/hdsd/scd/fsm/scd_cont_state.h | 38 + .../hdsd/scd/fsm/scd_cont_wrapper_if.h | 80 + .../dol/visitor/hdsd/scd/fsm/scd_stm_base.cpp | 103 + .../dol/visitor/hdsd/scd/fsm/scd_stm_base.h | 64 + .../dol/visitor/hdsd/scd/fsm/scd_stm_busy.cpp | 34 + .../dol/visitor/hdsd/scd/fsm/scd_stm_busy.h | 22 + .../dol/visitor/hdsd/scd/fsm/scd_stm_done.cpp | 64 + .../dol/visitor/hdsd/scd/fsm/scd_stm_done.h | 21 + .../dol/visitor/hdsd/scd/fsm/scd_stm_fail.cpp | 17 + .../dol/visitor/hdsd/scd/fsm/scd_stm_fail.h | 25 + .../visitor/hdsd/scd/fsm/scd_stm_failed.cpp | 10 + .../dol/visitor/hdsd/scd/fsm/scd_stm_failed.h | 25 + .../dol/visitor/hdsd/scd/fsm/scd_stm_idle.cpp | 50 + .../dol/visitor/hdsd/scd/fsm/scd_stm_idle.h | 20 + .../dol/visitor/hdsd/scd/fsm/scd_stm_init.cpp | 58 + .../dol/visitor/hdsd/scd/fsm/scd_stm_init.h | 27 + .../visitor/hdsd/scd/fsm/scd_stm_term_req.cpp | 59 + .../visitor/hdsd/scd/fsm/scd_stm_term_req.h | 23 + .../hdsd/scd/fsm/scd_stm_terminate.cpp | 40 + .../visitor/hdsd/scd/fsm/scd_stm_terminate.h | 21 + .../hdsd/scd/fsm/scd_stm_terminated.cpp | 8 + .../visitor/hdsd/scd/fsm/scd_stm_terminated.h | 21 + .../dol/visitor/hdsd/scd/fsm/scd_stm_time.cpp | 15 + .../dol/visitor/hdsd/scd/fsm/scd_stm_time.h | 19 + .../visitor/hdsd/scd/fsm/scd_stm_time_req.cpp | 64 + .../visitor/hdsd/scd/fsm/scd_stm_time_req.h | 22 + .../dol/visitor/hdsd/scd/fsm/scd_sts_base.cpp | 81 + .../dol/visitor/hdsd/scd/fsm/scd_sts_base.h | 101 + .../dol/visitor/hdsd/scd/fsm/scd_sts_busy.cpp | 36 + .../dol/visitor/hdsd/scd/fsm/scd_sts_busy.h | 21 + .../dol/visitor/hdsd/scd/fsm/scd_sts_done.cpp | 58 + .../dol/visitor/hdsd/scd/fsm/scd_sts_done.h | 27 + .../dol/visitor/hdsd/scd/fsm/scd_sts_fail.cpp | 17 + .../dol/visitor/hdsd/scd/fsm/scd_sts_fail.h | 28 + .../visitor/hdsd/scd/fsm/scd_sts_failed.cpp | 11 + .../dol/visitor/hdsd/scd/fsm/scd_sts_failed.h | 31 + .../dol/visitor/hdsd/scd/fsm/scd_sts_idle.cpp | 37 + .../dol/visitor/hdsd/scd/fsm/scd_sts_idle.h | 25 + .../dol/visitor/hdsd/scd/fsm/scd_sts_init.cpp | 70 + .../dol/visitor/hdsd/scd/fsm/scd_sts_init.h | 30 + .../visitor/hdsd/scd/fsm/scd_sts_term_ack.cpp | 36 + .../visitor/hdsd/scd/fsm/scd_sts_term_ack.h | 28 + .../hdsd/scd/fsm/scd_sts_terminated.cpp | 19 + .../visitor/hdsd/scd/fsm/scd_sts_terminated.h | 30 + .../dol/visitor/hdsd/scd/fsm/scd_sts_time.cpp | 21 + .../dol/visitor/hdsd/scd/fsm/scd_sts_time.h | 28 + .../visitor/hdsd/scd/fsm/scd_sts_time_ack.cpp | 36 + .../visitor/hdsd/scd/fsm/scd_sts_time_ack.h | 28 + .../visitor/hdsd/scd/fsm/scd_stsw_base.cpp | 125 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_base.h | 106 + .../visitor/hdsd/scd/fsm/scd_stsw_busy.cpp | 22 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_busy.h | 21 + .../visitor/hdsd/scd/fsm/scd_stsw_done.cpp | 39 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_done.h | 28 + .../visitor/hdsd/scd/fsm/scd_stsw_fail.cpp | 18 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_fail.h | 23 + .../visitor/hdsd/scd/fsm/scd_stsw_failed.cpp | 16 + .../visitor/hdsd/scd/fsm/scd_stsw_failed.h | 26 + .../visitor/hdsd/scd/fsm/scd_stsw_idle.cpp | 29 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_idle.h | 28 + .../visitor/hdsd/scd/fsm/scd_stsw_init.cpp | 58 + .../dol/visitor/hdsd/scd/fsm/scd_stsw_init.h | 31 + .../hdsd/scd/fsm/scd_stsw_term_ack.cpp | 36 + .../visitor/hdsd/scd/fsm/scd_stsw_term_ack.h | 28 + .../hdsd/scd/fsm/scd_stsw_term_req.cpp | 48 + .../visitor/hdsd/scd/fsm/scd_stsw_term_req.h | 32 + .../hdsd/scd/fsm/scd_stsw_terminate.cpp | 18 + .../visitor/hdsd/scd/fsm/scd_stsw_terminate.h | 23 + .../hdsd/scd/fsm/scd_stsw_terminated.cpp | 13 + .../hdsd/scd/fsm/scd_stsw_terminated.h | 25 + .../hdsd/scd/fsm/scd_stsw_time_ack.cpp | 42 + .../visitor/hdsd/scd/fsm/scd_stsw_time_ack.h | 29 + .../hdsd/scd/fsm/scd_stsw_time_req.cpp | 48 + .../visitor/hdsd/scd/fsm/scd_stsw_time_req.h | 33 + dol/src/dol/visitor/hdsd/scd/scd_chan_man.cpp | 216 ++ dol/src/dol/visitor/hdsd/scd/scd_chan_man.h | 117 + .../dol/visitor/hdsd/scd/scd_chan_wrapper.cpp | 201 ++ .../dol/visitor/hdsd/scd/scd_chan_wrapper.h | 95 + dol/src/dol/visitor/hdsd/scd/scd_command.cpp | 105 + dol/src/dol/visitor/hdsd/scd/scd_command.h | 108 + .../visitor/hdsd/scd/scd_command_reader.cpp | 119 + .../dol/visitor/hdsd/scd/scd_command_reader.h | 64 + .../visitor/hdsd/scd/scd_command_writer.cpp | 93 + .../dol/visitor/hdsd/scd/scd_command_writer.h | 61 + dol/src/dol/visitor/hdsd/scd/scd_cont_man.h | 38 + .../visitor/hdsd/scd/scd_cont_man_master.cpp | 93 + .../visitor/hdsd/scd/scd_cont_man_master.h | 86 + .../visitor/hdsd/scd/scd_cont_man_slave.cpp | 181 ++ .../dol/visitor/hdsd/scd/scd_cont_man_slave.h | 111 + .../hdsd/scd/scd_cont_slave_wrapper.cpp | 182 ++ .../visitor/hdsd/scd/scd_cont_slave_wrapper.h | 142 + .../dol/visitor/hdsd/scd/scd_exception.cpp | 35 + dol/src/dol/visitor/hdsd/scd/scd_exception.h | 57 + .../dol/visitor/hdsd/scd/scd_in_connector.cpp | 130 + .../dol/visitor/hdsd/scd/scd_in_connector.h | 46 + .../visitor/hdsd/scd/scd_init_listener.cpp | 126 + .../dol/visitor/hdsd/scd/scd_init_listener.h | 73 + dol/src/dol/visitor/hdsd/scd/scd_logging.cpp | 40 + dol/src/dol/visitor/hdsd/scd/scd_logging.h | 56 + .../visitor/hdsd/scd/scd_out_connector.cpp | 185 ++ .../dol/visitor/hdsd/scd/scd_out_connector.h | 110 + .../dol/visitor/hdsd/scd/scd_rem_chan_if.h | 67 + .../dol/visitor/hdsd/scd/scd_rem_fifo_in.cpp | 62 + .../dol/visitor/hdsd/scd/scd_rem_fifo_in.h | 83 + .../dol/visitor/hdsd/scd/scd_rem_fifo_out.cpp | 69 + .../dol/visitor/hdsd/scd/scd_rem_fifo_out.h | 83 + .../dol/visitor/hdsd/scd/scd_simulator.cpp | 218 ++ dol/src/dol/visitor/hdsd/scd/scd_simulator.h | 118 + .../dol/visitor/hdsd/scd/scd_sock_poller.cpp | 201 ++ .../dol/visitor/hdsd/scd/scd_sock_poller.h | 144 + dol/src/dol/visitor/hdsd/scd/scd_socket.cpp | 439 +++ dol/src/dol/visitor/hdsd/scd/scd_socket.h | 168 ++ dol/src/dol/visitor/package.html | 20 + .../ProtothreadMakefileVisitor.java | 63 + .../protothread/ProtothreadModuleVisitor.java | 170 ++ .../ProtothreadProcessVisitor.java | 343 +++ .../protothread/ProtothreadVisitor.java | 96 + dol/src/dol/visitor/protothread/lib/Fifo.cpp | 130 + dol/src/dol/visitor/protothread/lib/Fifo.h | 25 + .../protothread/lib/ProcessWrapper.cpp | 68 + .../visitor/protothread/lib/ProcessWrapper.h | 25 + .../visitor/protothread/lib/WindowedFifo.cpp | 203 ++ .../visitor/protothread/lib/WindowedFifo.h | 32 + dol/src/dol/visitor/protothread/lib/dol.h | 39 + .../visitor/protothread/lib/dolSupport.cpp | 89 + .../dol/visitor/protothread/lib/dolSupport.h | 76 + .../visitor/protothread/lib/lc-addrlabels.h | 85 + .../dol/visitor/protothread/lib/lc-switch.h | 76 + dol/src/dol/visitor/protothread/lib/lc.h | 132 + dol/src/dol/visitor/protothread/lib/pt-sem.h | 228 ++ dol/src/dol/visitor/protothread/lib/pt.h | 323 ++ .../visitor/rtems/RtemsMakefileVisitor.java | 296 ++ .../dol/visitor/rtems/RtemsModuleVisitor.java | 864 ++++++ .../visitor/rtems/RtemsProcessVisitor.java | 168 ++ .../visitor/rtems/RtemsPropertiesVisitor.java | 93 + .../dol/visitor/rtems/RtemsShaperVisitor.java | 50 + dol/src/dol/visitor/rtems/RtemsVisitor.java | 179 ++ dol/src/dol/visitor/rtems/lib/README | 80 + dol/src/dol/visitor/rtems/lib/appsupport.c | 136 + dol/src/dol/visitor/rtems/lib/appsupport.h | 79 + .../dol/visitor/rtems/lib/buffer_test_io.h | 117 + dol/src/dol/visitor/rtems/lib/dol.h | 26 + .../rtems/lib/process_wrapper_template.c | 116 + .../visitor/rtems/lib/rtems_process_wrapper.c | 253 ++ .../visitor/rtems/lib/rtems_process_wrapper.h | 106 + dol/src/dol/visitor/rtems/lib/system.h | 64 + dol/src/dol/visitor/rtems/lib/tmacros.h | 247 ++ .../dol/visitor/rtems/lib/traffic_shaping.c | 189 ++ .../dol/visitor/rtems/lib/traffic_shaping.h | 17 + .../dol/visitor/systemC/MakefileVisitor.java | 72 + .../dol/visitor/systemC/PNSystemCVisitor.java | 101 + .../dol/visitor/systemC/ProcessVisitor.java | 351 +++ .../dol/visitor/systemC/SCModuleVisitor.java | 232 ++ dol/src/dol/visitor/systemC/lib/dol.c | 81 + dol/src/dol/visitor/systemC/lib/dol.h | 77 + dol/src/dol/visitor/systemC/lib/dol_fifo.h | 432 +++ dol/src/dol/visitor/systemC/lib/dol_fifo_if.h | 90 + dol/src/dol/visitor/systemC/lib/dol_rp_ids.h | 28 + .../dol/visitor/systemC/lib/dol_sched_if.h | 46 + dol/src/dol/visitor/systemC/lib/simple_fifo.h | 182 ++ dol/src/dol/visitor/systemC/package.html | 20 + dol/src/dol/visitor/xml/MapXmlVisitor.java | 193 ++ dol/src/dol/visitor/xml/PNXmlVisitor.java | 217 ++ dol/src/dol/visitor/xml/package.html | 22 + .../dol/visitor/yapi/YapiMakefileVisitor.java | 76 + .../dol/visitor/yapi/YapiModuleVisitor.java | 126 + .../dol/visitor/yapi/YapiProcessVisitor.java | 309 ++ dol/src/dol/visitor/yapi/YapiVisitor.java | 95 + .../dol/visitor/yapi/lib/ProcessWrapper.cpp | 107 + dol/src/dol/visitor/yapi/lib/ProcessWrapper.h | 29 + dol/src/dol/visitor/yapi/lib/dol.h | 39 + dol/src/dol/visitor/yapi/lib/dolSupport.cpp | 81 + dol/src/dol/visitor/yapi/lib/dolSupport.h | 67 + dol/src/dol/visitor/yapi/lib/main.cpp | 41 + dol/src/dol/visitor/yapi/package.html | 20 + dol/src/dol_template.properties | 23 + dol/test/dolziptest.xml | 78 + dol/test/reference/example1_Linux.txt | 20 + dol/test/reference/example1_Windows XP.txt | 20 + dol/test/reference/example2_Linux.txt | 20 + dol/test/reference/example2_Windows XP.txt | 20 + dol/test/reference/example3_Linux.txt | 78 + dol/test/reference/example3_Windows XP.txt | 78 + dol/test/reference/example4_Linux.txt | 32 + dol/test/reference/example4_Windows XP.txt | 32 + dol/test/reference/example5_Linux.txt | 64 + dol/test/reference/example5_Windows XP.txt | 64 + dol/test/reference/example6_Linux.txt | 21 + dol/test/reference/example6_Windows XP.txt | 21 + dol/test/reference/example7_Linux.txt | 31 + dol/test/reference/example7_Windows XP.txt | 31 + .../reference/examplesingleprocess_Linux.txt | 30 + .../examplesingleprocess_Windows XP.txt | 30 + dol/test/runtests.xml | 96 + dol/test/src/test/test/TreeValidator.java | 78 + dol/test/src/test/util/Diff.java | 46 + dol/test/src/test/util/XMLValidator.java | 56 + dol/test/test.properties | 9 + 537 files changed, 59612 insertions(+) create mode 100644 dol/.classpath create mode 100644 dol/.project create mode 100644 dol/build.xml create mode 100644 dol/examples/arch/cell.xml create mode 100644 dol/examples/arch/mparm.xml create mode 100644 dol/examples/arch/rdt1.xml create mode 100644 dol/examples/arch/rdt8.xml create mode 100644 dol/examples/example1/example1.xml create mode 100644 dol/examples/example1/map_1.xml create mode 100644 dol/examples/example1/map_3.xml create mode 100644 dol/examples/example1/map_mparm.xml create mode 100644 dol/examples/example1/src/arraygen.c create mode 100644 dol/examples/example1/src/arraygen.h create mode 100644 dol/examples/example1/src/consumer.c create mode 100644 dol/examples/example1/src/consumer.h create mode 100644 dol/examples/example1/src/generator.c create mode 100644 dol/examples/example1/src/generator.h create mode 100644 dol/examples/example1/src/global.h create mode 100644 dol/examples/example1/src/printarray.c create mode 100644 dol/examples/example1/src/printarray.h create mode 100644 dol/examples/example1/src/quicksort.c create mode 100644 dol/examples/example1/src/quicksort.h create mode 100644 dol/examples/example1/src/square.c create mode 100644 dol/examples/example1/src/square.h create mode 100644 dol/examples/example2/example2.xml create mode 100644 dol/examples/example2/src/consumer.c create mode 100644 dol/examples/example2/src/consumer.h create mode 100644 dol/examples/example2/src/generator.c create mode 100644 dol/examples/example2/src/generator.h create mode 100644 dol/examples/example2/src/global.h create mode 100644 dol/examples/example2/src/square.c create mode 100644 dol/examples/example2/src/square.h create mode 100644 dol/examples/example3/example3.xml create mode 100644 dol/examples/example3/src/forward.c create mode 100644 dol/examples/example3/src/forward.h create mode 100644 dol/examples/example3/src/horizontal_consumer.c create mode 100644 dol/examples/example3/src/horizontal_consumer.h create mode 100644 dol/examples/example3/src/horizontal_generator.c create mode 100644 dol/examples/example3/src/horizontal_generator.h create mode 100644 dol/examples/example3/src/vertical_consumer.c create mode 100644 dol/examples/example3/src/vertical_consumer.h create mode 100644 dol/examples/example3/src/vertical_generator.c create mode 100644 dol/examples/example3/src/vertical_generator.h create mode 100644 dol/examples/example4/example4.xml create mode 100644 dol/examples/example4/src/addmult.c create mode 100644 dol/examples/example4/src/addmult.h create mode 100644 dol/examples/example4/src/constants.h create mode 100644 dol/examples/example4/src/consumer.c create mode 100644 dol/examples/example4/src/consumer.h create mode 100644 dol/examples/example4/src/generator.c create mode 100644 dol/examples/example4/src/generator.h create mode 100644 dol/examples/example5/example5.xml create mode 100644 dol/examples/example5/fft_script.m create mode 100644 dol/examples/example5/src/consumer.c create mode 100644 dol/examples/example5/src/consumer.h create mode 100644 dol/examples/example5/src/fft2.c create mode 100644 dol/examples/example5/src/fft2.h create mode 100644 dol/examples/example5/src/generator.c create mode 100644 dol/examples/example5/src/generator.h create mode 100644 dol/examples/example5/src/global.h create mode 100644 dol/examples/example6/example6.xml create mode 100644 dol/examples/example6/src/consumer.c create mode 100644 dol/examples/example6/src/consumer.h create mode 100644 dol/examples/example6/src/filter.c create mode 100644 dol/examples/example6/src/filter.h create mode 100644 dol/examples/example6/src/producer.c create mode 100644 dol/examples/example6/src/producer.h create mode 100644 dol/examples/example7/example7.xml create mode 100644 dol/examples/example7/filter_script.m create mode 100644 dol/examples/example7/src/consumer.c create mode 100644 dol/examples/example7/src/consumer.h create mode 100644 dol/examples/example7/src/filter.c create mode 100644 dol/examples/example7/src/filter.h create mode 100644 dol/examples/example7/src/global.h create mode 100644 dol/examples/example7/src/producer.c create mode 100644 dol/examples/example7/src/producer.h create mode 100644 dol/examples/examplecell/cell.xml create mode 100644 dol/examples/examplecell/examplecell.xml create mode 100644 dol/examples/examplecell/mapping.xml create mode 100644 dol/examples/examplecell/src/consumer.c create mode 100644 dol/examples/examplecell/src/consumer.h create mode 100644 dol/examples/examplecell/src/generator.c create mode 100644 dol/examples/examplecell/src/generator.h create mode 100644 dol/examples/examplecell/src/global.h create mode 100644 dol/examples/examplecell/src/square.c create mode 100644 dol/examples/examplecell/src/square.h create mode 100644 dol/examples/exampleproducerconsumer/exampleproducerconsumer.xml create mode 100644 dol/examples/exampleproducerconsumer/src/consumer.c create mode 100644 dol/examples/exampleproducerconsumer/src/consumer.h create mode 100644 dol/examples/exampleproducerconsumer/src/producer.c create mode 100644 dol/examples/exampleproducerconsumer/src/producer.h create mode 100644 dol/examples/examplesingleprocess/examplesingleprocess.xml create mode 100644 dol/examples/examplesingleprocess/src/task.c create mode 100644 dol/examples/examplesingleprocess/src/task.h create mode 100644 dol/examples/examplewindowedfifo/examplewindowedfifo.xml create mode 100644 dol/examples/examplewindowedfifo/src/consumer.c create mode 100644 dol/examples/examplewindowedfifo/src/consumer.h create mode 100644 dol/examples/examplewindowedfifo/src/generator.c create mode 100644 dol/examples/examplewindowedfifo/src/generator.h create mode 100644 dol/examples/examplewindowedfifo/src/global.h create mode 100644 dol/examples/examplewindowedfifo/src/square.c create mode 100644 dol/examples/examplewindowedfifo/src/square.h create mode 100644 dol/examples/runexample.xml create mode 100644 dol/examples/runprofiler.xml create mode 100644 dol/examples/schema/architecture.xsd create mode 100644 dol/examples/schema/createschemastex create mode 100644 dol/examples/schema/generics.xsd create mode 100644 dol/examples/schema/internal/architecture_internal.xsd create mode 100644 dol/examples/schema/internal/mapping_internal.xsd create mode 100644 dol/examples/schema/internal/processnetwork_internal.xsd create mode 100644 dol/examples/schema/mapping.xsd create mode 100644 dol/examples/schema/processnetwork.xsd create mode 100644 dol/jars/jdom.jar create mode 100644 dol/jars/xercesImpl.jar create mode 100644 dol/src/MANIFEST.MF create mode 100644 dol/src/docs/doxygen/doxygen.cfg create mode 100644 dol/src/docs/doxygen/footer.html create mode 100644 dol/src/docs/doxygen/header.html create mode 100644 dol/src/dol.properties create mode 100644 dol/src/dol/check/SanityCheck.java create mode 100644 dol/src/dol/check/package.html create mode 100644 dol/src/dol/datamodel/XmlTag.java create mode 100644 dol/src/dol/datamodel/architecture/ArchiConnection.java create mode 100644 dol/src/dol/datamodel/architecture/ArchiResource.java create mode 100644 dol/src/dol/datamodel/architecture/Architecture.java create mode 100644 dol/src/dol/datamodel/architecture/Configuration.java create mode 100644 dol/src/dol/datamodel/architecture/HWChannel.java create mode 100644 dol/src/dol/datamodel/architecture/Memory.java create mode 100644 dol/src/dol/datamodel/architecture/Node.java create mode 100644 dol/src/dol/datamodel/architecture/Path.java create mode 100644 dol/src/dol/datamodel/architecture/PortNode.java create mode 100644 dol/src/dol/datamodel/architecture/Processor.java create mode 100644 dol/src/dol/datamodel/architecture/ReadPath.java create mode 100644 dol/src/dol/datamodel/architecture/Variable.java create mode 100644 dol/src/dol/datamodel/architecture/WritePath.java create mode 100644 dol/src/dol/datamodel/architecture/package.html create mode 100644 dol/src/dol/datamodel/mapping/Binding.java create mode 100644 dol/src/dol/datamodel/mapping/CommunicationBinding.java create mode 100644 dol/src/dol/datamodel/mapping/ComputationBinding.java create mode 100644 dol/src/dol/datamodel/mapping/Configuration.java create mode 100644 dol/src/dol/datamodel/mapping/MapResource.java create mode 100644 dol/src/dol/datamodel/mapping/Mapping.java create mode 100644 dol/src/dol/datamodel/mapping/Schedule.java create mode 100644 dol/src/dol/datamodel/mapping/ScheduleEntry.java create mode 100644 dol/src/dol/datamodel/mapping/SchedulingPolicy.java create mode 100644 dol/src/dol/datamodel/mapping/Variable.java create mode 100644 dol/src/dol/datamodel/mapping/package.html create mode 100644 dol/src/dol/datamodel/package.html create mode 100644 dol/src/dol/datamodel/pn/Channel.java create mode 100644 dol/src/dol/datamodel/pn/Configuration.java create mode 100644 dol/src/dol/datamodel/pn/Connection.java create mode 100644 dol/src/dol/datamodel/pn/Port.java create mode 100644 dol/src/dol/datamodel/pn/Process.java create mode 100644 dol/src/dol/datamodel/pn/ProcessNetwork.java create mode 100644 dol/src/dol/datamodel/pn/ProfilingConfiguration.java create mode 100644 dol/src/dol/datamodel/pn/Resource.java create mode 100644 dol/src/dol/datamodel/pn/Schedulable.java create mode 100644 dol/src/dol/datamodel/pn/SourceCode.java create mode 100644 dol/src/dol/datamodel/pn/Variable.java create mode 100644 dol/src/dol/datamodel/pn/package.html create mode 100644 dol/src/dol/helper/flattener/ArchFlattener.java create mode 100644 dol/src/dol/helper/flattener/BugCatcher.java create mode 100644 dol/src/dol/helper/flattener/DomDocumentParser.java create mode 100644 dol/src/dol/helper/flattener/FlattenerHelper.java create mode 100644 dol/src/dol/helper/flattener/MappingFlattener.java create mode 100644 dol/src/dol/helper/flattener/PNFlattener.java create mode 100644 dol/src/dol/helper/flattener/SaxDocumentParser.java create mode 100644 dol/src/dol/helper/flattener/XMLFlattener.java create mode 100644 dol/src/dol/helper/flattener/package.html create mode 100644 dol/src/dol/helper/profiler/ChannelProfile.java create mode 100644 dol/src/dol/helper/profiler/Constants.java create mode 100644 dol/src/dol/helper/profiler/PNProfileSummarizer.java create mode 100644 dol/src/dol/helper/profiler/PortProfile.java create mode 100644 dol/src/dol/helper/profiler/ProcessProfile.java create mode 100644 dol/src/dol/helper/profiler/ProfileParser.java create mode 100644 dol/src/dol/helper/profiler/Profiler.java create mode 100644 dol/src/dol/helper/profiler/Range.java create mode 100644 dol/src/dol/helper/profiler/VSPLogFileProfiler.java create mode 100644 dol/src/dol/helper/profiler/WorkloadAnnotator.java create mode 100644 dol/src/dol/helper/profiler/package.html create mode 100644 dol/src/dol/helper/validator/XMLValidator.java create mode 100644 dol/src/dol/helper/validator/package.html create mode 100644 dol/src/dol/main/Main.java create mode 100644 dol/src/dol/main/Options.java create mode 100644 dol/src/dol/main/UserInterface.java create mode 100644 dol/src/dol/main/package.html create mode 100644 dol/src/dol/parser/xml/XmlErrorHandler.java create mode 100644 dol/src/dol/parser/xml/XmlParser.java create mode 100644 dol/src/dol/parser/xml/archischema/ArchiXmlParser.java create mode 100644 dol/src/dol/parser/xml/archischema/Xml2Archi.java create mode 100644 dol/src/dol/parser/xml/archischema/package.html create mode 100644 dol/src/dol/parser/xml/mapschema/MapXmlParser.java create mode 100644 dol/src/dol/parser/xml/mapschema/Xml2Map.java create mode 100644 dol/src/dol/parser/xml/mapschema/package.html create mode 100644 dol/src/dol/parser/xml/package.html create mode 100644 dol/src/dol/parser/xml/pnschema/PNXmlParser.java create mode 100644 dol/src/dol/parser/xml/pnschema/Xml2PN.java create mode 100644 dol/src/dol/parser/xml/pnschema/package.html create mode 100644 dol/src/dol/util/ApplicationGenerator.java create mode 100644 dol/src/dol/util/CheckXMLs.java create mode 100644 dol/src/dol/util/CodePrintStream.java create mode 100644 dol/src/dol/util/CodePrintString.java create mode 100644 dol/src/dol/util/Copier.java create mode 100644 dol/src/dol/util/JarCopier.java create mode 100644 dol/src/dol/util/SchemaLocation.java create mode 100644 dol/src/dol/util/Sed.java create mode 100644 dol/src/dol/util/package.html create mode 100644 dol/src/dol/visitor/ArchiVisitor.java create mode 100644 dol/src/dol/visitor/MapVisitor.java create mode 100644 dol/src/dol/visitor/PNVisitor.java create mode 100644 dol/src/dol/visitor/PipeAndFilter/PipeAndFilterMakefileVisitor.java create mode 100644 dol/src/dol/visitor/PipeAndFilter/PipeAndFilterModuleVisitor.java create mode 100644 dol/src/dol/visitor/PipeAndFilter/PipeAndFilterProcessVisitor.java create mode 100644 dol/src/dol/visitor/PipeAndFilter/PipeAndFilterVisitor.java create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Condition.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Condition.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Event.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Event.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Fifo.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Fifo.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Mutex.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Mutex.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/dol.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.cpp create mode 100644 dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.h create mode 100644 dol/src/dol/visitor/PipeAndFilter/package.html create mode 100644 dol/src/dol/visitor/Visitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeBuildFileVisitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeConstantVisitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeMakefileVisitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeModuleVisitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeProcessVisitor.java create mode 100644 dol/src/dol/visitor/cbe/CbeVisitor.java create mode 100644 dol/src/dol/visitor/cbe/lib/ProcessFifo.h create mode 100644 dol/src/dol/visitor/cbe/lib/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/cbe/lib/ProcessWrapperHelp.h create mode 100644 dol/src/dol/visitor/cbe/lib/ProcessWrapperPPE.c create mode 100644 dol/src/dol/visitor/cbe/lib/common.h create mode 100644 dol/src/dol/visitor/cbe/lib/common_ppu.h create mode 100644 dol/src/dol/visitor/cbe/lib/dol.h create mode 100644 dol/src/dol/visitor/cbe/lib/estimation.h create mode 100644 dol/src/dol/visitor/cbe/lib/free_align.h create mode 100644 dol/src/dol/visitor/cbe/lib/malloc_align.h create mode 100644 dol/src/dol/visitor/cbe/lib/ppu_main.h create mode 100644 dol/src/dol/visitor/cbe/lib/ppu_main_workloop.h create mode 100644 dol/src/dol/visitor/cbe/template/ppu_process_wrapper_template.c create mode 100644 dol/src/dol/visitor/cbe/template/spu_process_wrapper_template.c create mode 100644 dol/src/dol/visitor/cell/CellBuildFileVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellConstantVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellMakefileVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellMapping.java create mode 100644 dol/src/dol/visitor/cell/CellModuleVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellPPEVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellProcessVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellSPEVisitor.java create mode 100644 dol/src/dol/visitor/cell/CellVisitor.java create mode 100644 dol/src/dol/visitor/cell/lib/common.h create mode 100644 dol/src/dol/visitor/cell/lib/dol.h create mode 100644 dol/src/dol/visitor/cell/lib/estimation.h create mode 100644 dol/src/dol/visitor/cell/lib/free_align.h create mode 100644 dol/src/dol/visitor/cell/lib/malloc_align.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/FastCommunication.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/FastCommunication.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/Fifo.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/Fifo.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/common.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/common_ppu.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu/dolSupport.cpp create mode 100644 dol/src/dol/visitor/cell/lib/ppu/dolSupport.h create mode 100644 dol/src/dol/visitor/cell/lib/ppu_main.h create mode 100644 dol/src/dol/visitor/cell/lib/pt/lc-addrlabels.h create mode 100644 dol/src/dol/visitor/cell/lib/pt/lc-switch.h create mode 100644 dol/src/dol/visitor/cell/lib/pt/lc.h create mode 100644 dol/src/dol/visitor/cell/lib/pt/pt-sem.h create mode 100644 dol/src/dol/visitor/cell/lib/pt/pt.h create mode 100644 dol/src/dol/visitor/cell/lib/spu/FastCommunication.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/FastCommunication.h create mode 100644 dol/src/dol/visitor/cell/lib/spu/Fifo.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/Fifo.h create mode 100644 dol/src/dol/visitor/cell/lib/spu/WindowedFifo.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/WindowedFifo.h create mode 100644 dol/src/dol/visitor/cell/lib/spu/common.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/dolSupport.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/dolSupport.h create mode 100644 dol/src/dol/visitor/cell/lib/spu/proc_wrapper.cpp create mode 100644 dol/src/dol/visitor/cell/lib/spu/proc_wrapper.h create mode 100644 dol/src/dol/visitor/cell/lib/spu_mfcio_ext.h create mode 100644 dol/src/dol/visitor/cell/lib/spu_os.h create mode 100644 dol/src/dol/visitor/cell/template/spu_process_wrapper_template.cpp create mode 100644 dol/src/dol/visitor/cell/template/spu_process_wrapper_template.h create mode 100644 dol/src/dol/visitor/dot/ArchDotVisitor.java create mode 100644 dol/src/dol/visitor/dot/MapDotVisitor.java create mode 100644 dol/src/dol/visitor/dot/PNDotVisitor.java create mode 100644 dol/src/dol/visitor/dot/package.html create mode 100644 dol/src/dol/visitor/hds/HdsMakefileVisitor.java create mode 100644 dol/src/dol/visitor/hds/HdsModuleVisitor.java create mode 100644 dol/src/dol/visitor/hds/HdsProcessVisitor.java create mode 100644 dol/src/dol/visitor/hds/HdsVisitor.java create mode 100644 dol/src/dol/visitor/hds/lib/Fifo.cpp create mode 100644 dol/src/dol/visitor/hds/lib/Fifo.h create mode 100644 dol/src/dol/visitor/hds/lib/Performance_Extraction.cpp create mode 100644 dol/src/dol/visitor/hds/lib/Performance_Extraction.h create mode 100644 dol/src/dol/visitor/hds/lib/ProcessWrapper.cpp create mode 100644 dol/src/dol/visitor/hds/lib/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/hds/lib/WindowedFifo.cpp create mode 100644 dol/src/dol/visitor/hds/lib/WindowedFifo.h create mode 100644 dol/src/dol/visitor/hds/lib/dol.h create mode 100644 dol/src/dol/visitor/hds/lib/dolSupport.cpp create mode 100644 dol/src/dol/visitor/hds/lib/dolSupport.h create mode 100644 dol/src/dol/visitor/hds/lib/dol_sched_if.h create mode 100644 dol/src/dol/visitor/hds/lib/functional_trace.cpp create mode 100644 dol/src/dol/visitor/hds/lib/functional_trace.h create mode 100644 dol/src/dol/visitor/hds/lib/trace.h create mode 100644 dol/src/dol/visitor/hds/lib/xmlParser.cpp create mode 100644 dol/src/dol/visitor/hds/lib/xmlParser.h create mode 100644 dol/src/dol/visitor/hdsd/HdsdMakefileVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdModuleArchVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdModulePNVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdModuleVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdProcessVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdScriptVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/HdsdVisitor.java create mode 100644 dol/src/dol/visitor/hdsd/lib/dol.c create mode 100644 dol/src/dol/visitor/hdsd/lib/dol.h create mode 100644 dol/src/dol/visitor/hdsd/lib/dol_sched_if.h create mode 100644 dol/src/dol/visitor/hdsd/lib/simple_fifo.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm_if.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_state.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_wrapper_if.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.h create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_chan_man.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_chan_man.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command_reader.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command_reader.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command_writer.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_command_writer.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_man.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_exception.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_exception.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_in_connector.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_in_connector.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_init_listener.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_init_listener.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_logging.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_logging.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_out_connector.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_out_connector.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_rem_chan_if.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_in.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_in.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_out.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_out.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_simulator.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_simulator.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_sock_poller.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_sock_poller.h create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_socket.cpp create mode 100644 dol/src/dol/visitor/hdsd/scd/scd_socket.h create mode 100644 dol/src/dol/visitor/package.html create mode 100644 dol/src/dol/visitor/protothread/ProtothreadMakefileVisitor.java create mode 100644 dol/src/dol/visitor/protothread/ProtothreadModuleVisitor.java create mode 100644 dol/src/dol/visitor/protothread/ProtothreadProcessVisitor.java create mode 100644 dol/src/dol/visitor/protothread/ProtothreadVisitor.java create mode 100644 dol/src/dol/visitor/protothread/lib/Fifo.cpp create mode 100644 dol/src/dol/visitor/protothread/lib/Fifo.h create mode 100644 dol/src/dol/visitor/protothread/lib/ProcessWrapper.cpp create mode 100644 dol/src/dol/visitor/protothread/lib/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/protothread/lib/WindowedFifo.cpp create mode 100644 dol/src/dol/visitor/protothread/lib/WindowedFifo.h create mode 100644 dol/src/dol/visitor/protothread/lib/dol.h create mode 100644 dol/src/dol/visitor/protothread/lib/dolSupport.cpp create mode 100644 dol/src/dol/visitor/protothread/lib/dolSupport.h create mode 100644 dol/src/dol/visitor/protothread/lib/lc-addrlabels.h create mode 100644 dol/src/dol/visitor/protothread/lib/lc-switch.h create mode 100644 dol/src/dol/visitor/protothread/lib/lc.h create mode 100644 dol/src/dol/visitor/protothread/lib/pt-sem.h create mode 100644 dol/src/dol/visitor/protothread/lib/pt.h create mode 100644 dol/src/dol/visitor/rtems/RtemsMakefileVisitor.java create mode 100644 dol/src/dol/visitor/rtems/RtemsModuleVisitor.java create mode 100644 dol/src/dol/visitor/rtems/RtemsProcessVisitor.java create mode 100644 dol/src/dol/visitor/rtems/RtemsPropertiesVisitor.java create mode 100644 dol/src/dol/visitor/rtems/RtemsShaperVisitor.java create mode 100644 dol/src/dol/visitor/rtems/RtemsVisitor.java create mode 100644 dol/src/dol/visitor/rtems/lib/README create mode 100644 dol/src/dol/visitor/rtems/lib/appsupport.c create mode 100644 dol/src/dol/visitor/rtems/lib/appsupport.h create mode 100644 dol/src/dol/visitor/rtems/lib/buffer_test_io.h create mode 100644 dol/src/dol/visitor/rtems/lib/dol.h create mode 100644 dol/src/dol/visitor/rtems/lib/process_wrapper_template.c create mode 100644 dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.c create mode 100644 dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.h create mode 100644 dol/src/dol/visitor/rtems/lib/system.h create mode 100644 dol/src/dol/visitor/rtems/lib/tmacros.h create mode 100644 dol/src/dol/visitor/rtems/lib/traffic_shaping.c create mode 100644 dol/src/dol/visitor/rtems/lib/traffic_shaping.h create mode 100644 dol/src/dol/visitor/systemC/MakefileVisitor.java create mode 100644 dol/src/dol/visitor/systemC/PNSystemCVisitor.java create mode 100644 dol/src/dol/visitor/systemC/ProcessVisitor.java create mode 100644 dol/src/dol/visitor/systemC/SCModuleVisitor.java create mode 100644 dol/src/dol/visitor/systemC/lib/dol.c create mode 100644 dol/src/dol/visitor/systemC/lib/dol.h create mode 100644 dol/src/dol/visitor/systemC/lib/dol_fifo.h create mode 100644 dol/src/dol/visitor/systemC/lib/dol_fifo_if.h create mode 100644 dol/src/dol/visitor/systemC/lib/dol_rp_ids.h create mode 100644 dol/src/dol/visitor/systemC/lib/dol_sched_if.h create mode 100644 dol/src/dol/visitor/systemC/lib/simple_fifo.h create mode 100644 dol/src/dol/visitor/systemC/package.html create mode 100644 dol/src/dol/visitor/xml/MapXmlVisitor.java create mode 100644 dol/src/dol/visitor/xml/PNXmlVisitor.java create mode 100644 dol/src/dol/visitor/xml/package.html create mode 100644 dol/src/dol/visitor/yapi/YapiMakefileVisitor.java create mode 100644 dol/src/dol/visitor/yapi/YapiModuleVisitor.java create mode 100644 dol/src/dol/visitor/yapi/YapiProcessVisitor.java create mode 100644 dol/src/dol/visitor/yapi/YapiVisitor.java create mode 100644 dol/src/dol/visitor/yapi/lib/ProcessWrapper.cpp create mode 100644 dol/src/dol/visitor/yapi/lib/ProcessWrapper.h create mode 100644 dol/src/dol/visitor/yapi/lib/dol.h create mode 100644 dol/src/dol/visitor/yapi/lib/dolSupport.cpp create mode 100644 dol/src/dol/visitor/yapi/lib/dolSupport.h create mode 100644 dol/src/dol/visitor/yapi/lib/main.cpp create mode 100644 dol/src/dol/visitor/yapi/package.html create mode 100644 dol/src/dol_template.properties create mode 100644 dol/test/dolziptest.xml create mode 100644 dol/test/reference/example1_Linux.txt create mode 100644 dol/test/reference/example1_Windows XP.txt create mode 100644 dol/test/reference/example2_Linux.txt create mode 100644 dol/test/reference/example2_Windows XP.txt create mode 100644 dol/test/reference/example3_Linux.txt create mode 100644 dol/test/reference/example3_Windows XP.txt create mode 100644 dol/test/reference/example4_Linux.txt create mode 100644 dol/test/reference/example4_Windows XP.txt create mode 100644 dol/test/reference/example5_Linux.txt create mode 100644 dol/test/reference/example5_Windows XP.txt create mode 100644 dol/test/reference/example6_Linux.txt create mode 100644 dol/test/reference/example6_Windows XP.txt create mode 100644 dol/test/reference/example7_Linux.txt create mode 100644 dol/test/reference/example7_Windows XP.txt create mode 100644 dol/test/reference/examplesingleprocess_Linux.txt create mode 100644 dol/test/reference/examplesingleprocess_Windows XP.txt create mode 100644 dol/test/runtests.xml create mode 100644 dol/test/src/test/test/TreeValidator.java create mode 100644 dol/test/src/test/util/Diff.java create mode 100644 dol/test/src/test/util/XMLValidator.java create mode 100644 dol/test/test.properties diff --git a/dol/.classpath b/dol/.classpath new file mode 100644 index 0000000..b331c6d --- /dev/null +++ b/dol/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dol/.project b/dol/.project new file mode 100644 index 0000000..cb7f5e5 --- /dev/null +++ b/dol/.project @@ -0,0 +1,17 @@ + + + DOL_tecmp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/dol/build.xml b/dol/build.xml new file mode 100644 index 0000000..a47b050 --- /dev/null +++ b/dol/build.xml @@ -0,0 +1,183 @@ + + + + + + Ant build file for DOL and its documentation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${api.name}]]> + Copyright © 2005-2007 Computer Engineering and Networks Laboratory (TIK), ETH Zürich. All Rights Reserved.]]> + + + + + + + + + + + + + + + + + diff --git a/dol/examples/arch/cell.xml b/dol/examples/arch/cell.xml new file mode 100644 index 0000000..c1397f3 --- /dev/null +++ b/dol/examples/arch/cell.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/arch/mparm.xml b/dol/examples/arch/mparm.xml new file mode 100644 index 0000000..af637da --- /dev/null +++ b/dol/examples/arch/mparm.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/arch/rdt1.xml b/dol/examples/arch/rdt1.xml new file mode 100644 index 0000000..aa385c7 --- /dev/null +++ b/dol/examples/arch/rdt1.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/arch/rdt8.xml b/dol/examples/arch/rdt8.xml new file mode 100644 index 0000000..5f7189b --- /dev/null +++ b/dol/examples/arch/rdt8.xml @@ -0,0 +1,2059 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dol/examples/example1/example1.xml b/dol/examples/example1/example1.xml new file mode 100644 index 0000000..654aaad --- /dev/null +++ b/dol/examples/example1/example1.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example1/map_1.xml b/dol/examples/example1/map_1.xml new file mode 100644 index 0000000..46820c3 --- /dev/null +++ b/dol/examples/example1/map_1.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/dol/examples/example1/map_3.xml b/dol/examples/example1/map_3.xml new file mode 100644 index 0000000..976172a --- /dev/null +++ b/dol/examples/example1/map_3.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/dol/examples/example1/map_mparm.xml b/dol/examples/example1/map_mparm.xml new file mode 100644 index 0000000..a68d309 --- /dev/null +++ b/dol/examples/example1/map_mparm.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example1/src/arraygen.c b/dol/examples/example1/src/arraygen.c new file mode 100644 index 0000000..14b3fed --- /dev/null +++ b/dol/examples/example1/src/arraygen.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "arragygen.h" + +// initialization function +void arraygen_init(DOLProcess *p) { + p->local->index = 0; + srand((unsigned) time(NULL)); + //p->local->len = LENGTH; +} + +int arraygen_fire(DOLProcess *p) { + + + /* generate a random integer array of length L */ + //int num = rand() % 1000; + + int i; + int len = LENGTH; + int * array; + array = (int *) malloc(sizeof(int)*len); + + for(i=0;i +#include "global.h" + +#define PORT_OUT1 1 +#define PORT_OUT2 2 + + +typedef struct _local_states { + int index; +} Arraygen_State; + +void arraygen_init(DOLProcess *); +int arraygen_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example1/src/consumer.c b/dol/examples/example1/src/consumer.c new file mode 100644 index 0000000..711614b --- /dev/null +++ b/dol/examples/example1/src/consumer.c @@ -0,0 +1,26 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) { + sprintf(p->local->name, "consumer"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int consumer_fire(DOLProcess *p) { + float c; + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &c, sizeof(float), p); + printf("%s: %f\n", p->local->name, c); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example1/src/consumer.h b/dol/examples/example1/src/consumer.h new file mode 100644 index 0000000..677609a --- /dev/null +++ b/dol/examples/example1/src/consumer.h @@ -0,0 +1,18 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_IN 1 + +typedef struct _local_states { + char name[10]; + int index; + int len; +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example1/src/generator.c b/dol/examples/example1/src/generator.c new file mode 100644 index 0000000..88c801f --- /dev/null +++ b/dol/examples/example1/src/generator.c @@ -0,0 +1,27 @@ +#include +#include + +#include "generator.h" + +// initialization function +void generator_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int generator_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + float x = (float)p->local->index; + DOL_write((void*)PORT_OUT, &(x), sizeof(float), p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example1/src/generator.h b/dol/examples/example1/src/generator.h new file mode 100644 index 0000000..b938a38 --- /dev/null +++ b/dol/examples/example1/src/generator.h @@ -0,0 +1,17 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include +#include "global.h" + +#define PORT_OUT 1 + +typedef struct _local_states { + int index; + int len; +} Generator_State; + +void generator_init(DOLProcess *); +int generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example1/src/global.h b/dol/examples/example1/src/global.h new file mode 100644 index 0000000..78aaffd --- /dev/null +++ b/dol/examples/example1/src/global.h @@ -0,0 +1,9 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#include + +#define ARRAY_LEN 10 +#define DEBUG 1 + +#endif diff --git a/dol/examples/example1/src/printarray.c b/dol/examples/example1/src/printarray.c new file mode 100644 index 0000000..b140b96 --- /dev/null +++ b/dol/examples/example1/src/printarray.c @@ -0,0 +1,25 @@ +#include + +#include "printarray.h" + +void printarray_init(DOLProcess *p) { + p->local->index = 0; +} + +int printarray_fire(DOLProcess *p) { + + int len; + int * array; + DOL_read((void*)PORT_IN1, &len, sizeof(int), p); + DOL_read((void*)PORT_IN2, array, sizeof(int)*len, p); + + printf("sorted output\n"); + for(i=0;i +#include "global.h" + +#define PORT_IN1 1 +#define PORT_IN2 2 + +typedef struct _local_states { + int index; +} Printarray_State; + +void printarray_init(DOLProcess *); +int printarray_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example1/src/quicksort.c b/dol/examples/example1/src/quicksort.c new file mode 100644 index 0000000..5512fda --- /dev/null +++ b/dol/examples/example1/src/quicksort.c @@ -0,0 +1,120 @@ +#include +#include + +#include "quicksort.h" + +int smaller_arr(int *dst, int *src, int pivot,int len) +{ + int i; + int num=0; + for(i=0;ipivot) + { + dst[num++]=src[i]; + } + } + return num; +} + +int median_const(int len, int * array) +{ + int i,j; + int desired_rank = ceil(len/2); + + for(i=0;iarray[i]) + rank++; + } + + if(rank==desired_rank) + return array[i]; + } + return array[0]; +} + +int select_med(int size, int * array) +{ + int ret; + int * array_of_median; + int len = ceil(size/5); + int i,j; + + /* divide array into groups of five elements */ + array_of_median = malloc(sizeof(int)*len); + + for(i=0;ilocal->index = 0; +} + +int quicksort_fire(DOLProcess *p) { + + int len=LENGTH; + int * array; + int median; + int down,up; + + /* receive the 'size' / 'array' */ + DOL_read((void*)PORT_IN1, &len, sizeof(int), p); + DOL_read((void*)PORT_IN2, array, sizeof(int)*len, p); + + /* selection */ + median = select_med(len, array); + + /* divide */ + down = 0; up =0; + down=smaller_arr(arr1,array,median,len); + up=bigger_arr(arr2,array,median,len); + + // down + up == len + + /* sort */ + quick + + /* merge */ + + /* send the 'size' / 'sorted array' */ + DOL_write((void*)PORT_OUT1, &len, sizeof(int), p); + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example1/src/quicksort.h b/dol/examples/example1/src/quicksort.h new file mode 100644 index 0000000..09227ed --- /dev/null +++ b/dol/examples/example1/src/quicksort.h @@ -0,0 +1,21 @@ +#ifndef QUICKSORT_H +#define QUICKSORT_H + +#include +#include "global.h" + +#define PORT_IN1 1 +#define PORT_IN2 2 +#define PORT_OUT1 3 +#define PORT_OUT2 4 + +typedef struct _local_states { + int index; + int * arr1; + int * arr2; +} Quicksort_State; + +void quicksort_init(DOLProcess *); +int quicksort_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example1/src/square.c b/dol/examples/example1/src/square.c new file mode 100644 index 0000000..126378b --- /dev/null +++ b/dol/examples/example1/src/square.c @@ -0,0 +1,27 @@ +#include + +#include "square.h" + +void square_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int square_fire(DOLProcess *p) { + float i; + + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &i, sizeof(float), p); + i = i*i; + DOL_write((void*)PORT_OUT, &i, sizeof(float), p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example1/src/square.h b/dol/examples/example1/src/square.h new file mode 100644 index 0000000..2db7edc --- /dev/null +++ b/dol/examples/example1/src/square.h @@ -0,0 +1,18 @@ +#ifndef SQUARE_H +#define SQUARE_H + +#include +#include "global.h" + +#define PORT_IN 1 +#define PORT_OUT 2 + +typedef struct _local_states { + int index; + int len; +} Square_State; + +void square_init(DOLProcess *); +int square_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example2/example2.xml b/dol/examples/example2/example2.xml new file mode 100644 index 0000000..9a915a4 --- /dev/null +++ b/dol/examples/example2/example2.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example2/src/consumer.c b/dol/examples/example2/src/consumer.c new file mode 100644 index 0000000..711614b --- /dev/null +++ b/dol/examples/example2/src/consumer.c @@ -0,0 +1,26 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) { + sprintf(p->local->name, "consumer"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int consumer_fire(DOLProcess *p) { + float c; + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &c, sizeof(float), p); + printf("%s: %f\n", p->local->name, c); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example2/src/consumer.h b/dol/examples/example2/src/consumer.h new file mode 100644 index 0000000..2a4a545 --- /dev/null +++ b/dol/examples/example2/src/consumer.h @@ -0,0 +1,18 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_IN 100 + +typedef struct _local_states { + char name[10]; + int index; + int len; +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example2/src/generator.c b/dol/examples/example2/src/generator.c new file mode 100644 index 0000000..88c801f --- /dev/null +++ b/dol/examples/example2/src/generator.c @@ -0,0 +1,27 @@ +#include +#include + +#include "generator.h" + +// initialization function +void generator_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int generator_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + float x = (float)p->local->index; + DOL_write((void*)PORT_OUT, &(x), sizeof(float), p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example2/src/generator.h b/dol/examples/example2/src/generator.h new file mode 100644 index 0000000..4f05280 --- /dev/null +++ b/dol/examples/example2/src/generator.h @@ -0,0 +1,17 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include +#include "global.h" + +#define PORT_OUT 10 + +typedef struct _local_states { + int index; + int len; +} Generator_State; + +void generator_init(DOLProcess *); +int generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example2/src/global.h b/dol/examples/example2/src/global.h new file mode 100644 index 0000000..dfcbb84 --- /dev/null +++ b/dol/examples/example2/src/global.h @@ -0,0 +1,6 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define LENGTH 20 + +#endif diff --git a/dol/examples/example2/src/square.c b/dol/examples/example2/src/square.c new file mode 100644 index 0000000..126378b --- /dev/null +++ b/dol/examples/example2/src/square.c @@ -0,0 +1,27 @@ +#include + +#include "square.h" + +void square_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int square_fire(DOLProcess *p) { + float i; + + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &i, sizeof(float), p); + i = i*i; + DOL_write((void*)PORT_OUT, &i, sizeof(float), p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/example2/src/square.h b/dol/examples/example2/src/square.h new file mode 100644 index 0000000..27b2bcc --- /dev/null +++ b/dol/examples/example2/src/square.h @@ -0,0 +1,18 @@ +#ifndef SQUARE_H +#define SQUARE_H + +#include +#include "global.h" + +#define PORT_IN 0 +#define PORT_OUT 1 + +typedef struct _local_states { + int index; + int len; +} Square_State; + +void square_init(DOLProcess *); +int square_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example3/example3.xml b/dol/examples/example3/example3.xml new file mode 100644 index 0000000..68859ae --- /dev/null +++ b/dol/examples/example3/example3.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example3/src/forward.c b/dol/examples/example3/src/forward.c new file mode 100644 index 0000000..a3b7e81 --- /dev/null +++ b/dol/examples/example3/src/forward.c @@ -0,0 +1,19 @@ +#include + +#include "forward.h" + +void forward_init(DOLProcess *p) { + sprintf(p->local->name,"Forward"); +} + +int forward_fire(DOLProcess *p) { + char c; + char c2; + + DOL_read((void*)PORT_IN1, &c, 1, p); + DOL_read((void*)PORT_IN2, &c2, 1, p); + DOL_write((void*)PORT_OUT1, &c, 1, p); + DOL_write((void*)PORT_OUT2, &c2, 1, p); + + return 0; +} diff --git a/dol/examples/example3/src/forward.h b/dol/examples/example3/src/forward.h new file mode 100644 index 0000000..62d4309 --- /dev/null +++ b/dol/examples/example3/src/forward.h @@ -0,0 +1,18 @@ +#ifndef FORWARD_H +#define FORWARD_H + +#include + +#define PORT_IN1 "west" +#define PORT_IN2 "north" +#define PORT_OUT1 "east" +#define PORT_OUT2 "south" + +typedef struct _local_states { + char name[20]; +} Forward_State; + +void forward_init(DOLProcess *); +int forward_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example3/src/horizontal_consumer.c b/dol/examples/example3/src/horizontal_consumer.c new file mode 100644 index 0000000..3849aed --- /dev/null +++ b/dol/examples/example3/src/horizontal_consumer.c @@ -0,0 +1,16 @@ +#include + +#include "horizontal_consumer.h" + +void horizontal_consumer_init(DOLProcess *p) { + sprintf(p->local->name, "h_consumer"); +} + +int horizontal_consumer_fire(DOLProcess *p) { + char c; + DOL_read((void*)PORT_IN, &c, 1, p); + printf("%s: %c\n", p->local->name, c); + + return 0; +} + diff --git a/dol/examples/example3/src/horizontal_consumer.h b/dol/examples/example3/src/horizontal_consumer.h new file mode 100644 index 0000000..f09bffb --- /dev/null +++ b/dol/examples/example3/src/horizontal_consumer.h @@ -0,0 +1,15 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include + +#define PORT_IN "h_in" + +typedef struct _local_states { + char name[20]; +} Horizontal_consumer_State; + +void horizontal_consumer_init(DOLProcess *); +int horizontal_consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example3/src/horizontal_generator.c b/dol/examples/example3/src/horizontal_generator.c new file mode 100644 index 0000000..501ff1b --- /dev/null +++ b/dol/examples/example3/src/horizontal_generator.c @@ -0,0 +1,24 @@ +#include +#include + +#include "horizontal_generator.h" + +// initialization function +void horizontal_generator_init(DOLProcess *p) { + sprintf(p->local->str, "nopqrstuvwxyz"); + p->local->index = 0; + p->local->len = strlen(p->local->str); +} + +int horizontal_generator_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + DOL_write((void*)PORT_OUT, (p->local->str + p->local->index), 1, p); + p->local->index++; + } + else { + DOL_detach(p); + } + return 0; +} + diff --git a/dol/examples/example3/src/horizontal_generator.h b/dol/examples/example3/src/horizontal_generator.h new file mode 100644 index 0000000..fdc8a3c --- /dev/null +++ b/dol/examples/example3/src/horizontal_generator.h @@ -0,0 +1,17 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include + +#define PORT_OUT "h_out" + +typedef struct _local_states { + int index; + char str[25]; + int len; +} Horizontal_generator_State; + +void horizontal_generator_init(DOLProcess *); +int horizontal_generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example3/src/vertical_consumer.c b/dol/examples/example3/src/vertical_consumer.c new file mode 100644 index 0000000..a4f166b --- /dev/null +++ b/dol/examples/example3/src/vertical_consumer.c @@ -0,0 +1,15 @@ +#include + +#include "vertical_consumer.h" + +void vertical_consumer_init(DOLProcess *p) { + sprintf(p->local->name, "v_consumer"); +} + +int vertical_consumer_fire(DOLProcess *p) { + char c; + DOL_read((void*)PORT_IN, &c, 1, p); + printf("%s: %c\n", p->local->name, c); + + return 0; +} diff --git a/dol/examples/example3/src/vertical_consumer.h b/dol/examples/example3/src/vertical_consumer.h new file mode 100644 index 0000000..8dd538b --- /dev/null +++ b/dol/examples/example3/src/vertical_consumer.h @@ -0,0 +1,15 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include + +#define PORT_IN "v_in" + +typedef struct _local_states { + char name[20]; +} Vertical_consumer_State; + +void vertical_consumer_init(DOLProcess *); +int vertical_consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example3/src/vertical_generator.c b/dol/examples/example3/src/vertical_generator.c new file mode 100644 index 0000000..5dfa100 --- /dev/null +++ b/dol/examples/example3/src/vertical_generator.c @@ -0,0 +1,23 @@ +#include +#include + +#include "vertical_generator.h" + +// initialization function +void vertical_generator_init(DOLProcess *p) { + sprintf(p->local->str,"abcdefghijklm"); + p->local->index = 0; + p->local->len = strlen(p->local->str); +} + +int vertical_generator_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + DOL_write((void*)PORT_OUT, (p->local->str + p->local->index), 1, p); + p->local->index++; + } else { + DOL_detach(p); + } + + return 0; +} diff --git a/dol/examples/example3/src/vertical_generator.h b/dol/examples/example3/src/vertical_generator.h new file mode 100644 index 0000000..f6dec58 --- /dev/null +++ b/dol/examples/example3/src/vertical_generator.h @@ -0,0 +1,17 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include + +#define PORT_OUT "v_out" + +typedef struct _local_states { + int index; + char str[20]; + int len; +} Vertical_generator_State; + +void vertical_generator_init(DOLProcess *); +int vertical_generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example4/example4.xml b/dol/examples/example4/example4.xml new file mode 100644 index 0000000..f9db3d6 --- /dev/null +++ b/dol/examples/example4/example4.xml @@ -0,0 +1,344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example4/src/addmult.c b/dol/examples/example4/src/addmult.c new file mode 100644 index 0000000..00ef15e --- /dev/null +++ b/dol/examples/example4/src/addmult.c @@ -0,0 +1,28 @@ +#include +#include "addmult.h" + +void addmult_init(DOLProcess *p) +{ + sprintf(p->local->id, "addmult_%d_%d_%d", + GETINDEX(0), + GETINDEX(1), + GETINDEX(2)); +} + + +int addmult_fire(DOLProcess *p) +{ + float factor1, factor2, summand; + + DOL_read((void*)PORT_FACTOR1, &factor1, sizeof(float), p); + DOL_read((void*)PORT_FACTOR2, &factor2, sizeof(float), p); + DOL_read((void*)PORT_SUMMAND, &summand, sizeof(float), p); + p->local->sum = factor1 * factor2 + summand; + DOL_write((void*)PORT_SUM, &(p->local->sum), sizeof(float), p); + + printf("%15s: %f * %f + %f = %f\n", + p->local->id, factor1, factor2, summand, p->local->sum); + + return 0; +} + diff --git a/dol/examples/example4/src/addmult.h b/dol/examples/example4/src/addmult.h new file mode 100644 index 0000000..7eb7c3a --- /dev/null +++ b/dol/examples/example4/src/addmult.h @@ -0,0 +1,20 @@ +#ifndef ADDMULT_H +#define ADDMULT_H + +#include + +#define PORT_FACTOR1 "factor1" +#define PORT_FACTOR2 "factor2" +#define PORT_SUMMAND "summand" +#define PORT_SUM "sum" + +typedef struct _local_states +{ + char id[14]; + float sum; +} Addmult_State; + +void addmult_init(DOLProcess *); +int addmult_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example4/src/constants.h b/dol/examples/example4/src/constants.h new file mode 100644 index 0000000..36d73b2 --- /dev/null +++ b/dol/examples/example4/src/constants.h @@ -0,0 +1,20 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +/* +#define NUMBER_OF_ROWS_COLS 1 +#define MATRIX_A_INITIAL_VALUE { 1.0 } +#define MATRIX_B_INITIAL_VALUE { 2.5 } +*/ + +#define NUMBER_OF_ROWS_COLS 2 +#define MATRIX_A_INITIAL_VALUE { {1.0, 2.0 }, { 3.0, 4.0 } } +#define MATRIX_B_INITIAL_VALUE { {0.0, -1.0 }, { -2.0, -3.0 } } + +/* +#define NUMBER_OF_ROWS_COLS 3 +#define MATRIX_A_INITIAL_VALUE { {1.0, 2.0, 3.0 }, {4.0, 5.0, -4.0 }, {-3.0, -2.0, -1.0} } +#define MATRIX_B_INITIAL_VALUE { {1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0} } +*/ + +#endif diff --git a/dol/examples/example4/src/consumer.c b/dol/examples/example4/src/consumer.c new file mode 100644 index 0000000..ebd4e98 --- /dev/null +++ b/dol/examples/example4/src/consumer.c @@ -0,0 +1,29 @@ +#include + +#include "consumer.h" + +void output_consumer_init(DOLProcess *p) +{ + ; //nothing to be done here +} + +int output_consumer_fire(DOLProcess *p) +{ + CREATEPORTVAR(port); + + for (p->local->row = 0; p->local->row < NUMBER_OF_ROWS_COLS; p->local->row++) + { + for (p->local->col = 0; p->local->col < NUMBER_OF_ROWS_COLS; p->local->col++) + { + CREATEPORT(port, PORT_MATRIXC, 2, + p->local->row, NUMBER_OF_ROWS_COLS, + p->local->col, NUMBER_OF_ROWS_COLS); + + DOL_read((void*)port, &p->local->matrixC_value, sizeof(float), p); + printf("%15s: matrixC[%d][%d]: %f\n", + "output_consumer", p->local->row, p->local->col, p->local->matrixC_value); + } + } + return 0; +} + diff --git a/dol/examples/example4/src/consumer.h b/dol/examples/example4/src/consumer.h new file mode 100644 index 0000000..7406a75 --- /dev/null +++ b/dol/examples/example4/src/consumer.h @@ -0,0 +1,18 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include "constants.h" +#include + +#define PORT_MATRIXC "matrixC" + +typedef struct _local_states +{ + unsigned row, col; + float matrixC_value; +} Output_consumer_State; + +void output_consumer_init(DOLProcess *); +int output_consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example4/src/generator.c b/dol/examples/example4/src/generator.c new file mode 100644 index 0000000..60f3962 --- /dev/null +++ b/dol/examples/example4/src/generator.c @@ -0,0 +1,65 @@ +#include +#include + +#include "generator.h" + + +Input_generator_State input_generator_state = + { matrixA: MATRIX_A_INITIAL_VALUE, matrixB: MATRIX_B_INITIAL_VALUE }; + +void input_generator_init(DOLProcess *p) +{ + for (p->local->row = 0; p->local->row < NUMBER_OF_ROWS_COLS; p->local->row++) + { + for (p->local->col = 0; p->local->col < NUMBER_OF_ROWS_COLS; p->local->col++) + { + p->local->matrixA[p->local->row][p->local->col] = (NUMBER_OF_ROWS_COLS * p->local->row) + p->local->col + 1; + p->local->matrixB[p->local->row][p->local->col] = -((NUMBER_OF_ROWS_COLS * p->local->row) + p->local->col); + } + } +} + + +int input_generator_fire(DOLProcess *p) +{ + CREATEPORTVAR(port); + + for (p->local->row = 0; p->local->row < NUMBER_OF_ROWS_COLS; p->local->row++) + { + for (p->local->col = 0; p->local->col < NUMBER_OF_ROWS_COLS; p->local->col++) + { + CREATEPORT(port, PORT_ZEROINPUT, 1, + p->local->row * NUMBER_OF_ROWS_COLS + p->local->col, NUMBER_OF_ROWS_COLS + * NUMBER_OF_ROWS_COLS); + printf("%15s: Write to zeroinput_%d: %f\n", "input_generator", + p->local->row * NUMBER_OF_ROWS_COLS + p->local->col, 0.0); + DOL_write((void*)port, &(p->local->zero), sizeof(float), p); + + for (p->local->i = 0; p->local->i < NUMBER_OF_ROWS_COLS; p->local->i++) + { + CREATEPORT(port, PORT_MATRIXA, 3, + p->local->row, NUMBER_OF_ROWS_COLS, + p->local->col, NUMBER_OF_ROWS_COLS, + p->local->i, NUMBER_OF_ROWS_COLS); + printf("%15s: Write to matrixA_%d_%d_%d: %f\n", + "input_generator", p->local->i, p->local->row, p->local->col, + p->local->matrixA[p->local->row][p->local->col]); + DOL_write((void*)port, &(p->local->matrixA[p->local->row][p->local->col]), + sizeof(float), p); + CREATEPORT(port, PORT_MATRIXB, 3, + p->local->row, NUMBER_OF_ROWS_COLS, + p->local->col, NUMBER_OF_ROWS_COLS, + p->local->i, NUMBER_OF_ROWS_COLS); + printf("%15s: Write to matrixB_%d_%d_%d: %f\n", + "input_generator", p->local->i, p->local->row, p->local->col, + p->local->matrixB[p->local->row][p->local->col]); + DOL_write((void*)port, &(p->local->matrixB[p->local->row][p->local->col]), + sizeof(float), p); + } + } + } + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example4/src/generator.h b/dol/examples/example4/src/generator.h new file mode 100644 index 0000000..c4ad94e --- /dev/null +++ b/dol/examples/example4/src/generator.h @@ -0,0 +1,22 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include "constants.h" +#include + +#define PORT_MATRIXA "matrixA" +#define PORT_MATRIXB "matrixB" +#define PORT_ZEROINPUT "zeroinput" + +typedef struct _local_states +{ + float matrixA[NUMBER_OF_ROWS_COLS][NUMBER_OF_ROWS_COLS]; + float matrixB[NUMBER_OF_ROWS_COLS][NUMBER_OF_ROWS_COLS]; + int row, col, i; + float zero; +} Input_generator_State; + +void input_generator_init(DOLProcess *); +int input_generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example5/example5.xml b/dol/examples/example5/example5.xml new file mode 100644 index 0000000..659342f --- /dev/null +++ b/dol/examples/example5/example5.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example5/fft_script.m b/dol/examples/example5/fft_script.m new file mode 100644 index 0000000..4a418ea --- /dev/null +++ b/dol/examples/example5/fft_script.m @@ -0,0 +1,146 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Matlab script to compute the reference result for the FFT example. +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +close all; +clear all; +clc; + +wNk = inline('exp(-j * 2 * pi * k / N)', 'N', 'k'); + +%coefficients produced by c's random number generator in producer_fire() +x = [ -9 + j * 4 + -2 + j * 0 + -3 + j * 0 + 8 + j * 3 + 6 + j * 0 + -7 + j * -9 + -5 + j * 2 + 1 + j * 2 + 8 + j * -4 + -6 + j * -4 + 9 + j * -2 + -8 + j * -4 + -3 + j * 1 + -9 + j * 7 + -9 + j * -9 + -7 + j * 7 + 4 + j * -6 + 5 + j * -5 + 4 + j * 8 + 7 + j * -6 + -3 + j * 9 + -6 + j * -2 + -8 + j * -9 + 2 + j * 7 + -5 + j * -7 + 7 + j * -9 + 0 + j * 9 + 5 + j * 1 + -8 + j * 1 + 5 + j * 6 + -2 + j * -8 + 6 + j * -8 + -5 + j * 7 + 5 + j * -9 + 8 + j * -5 + -6 + j * 9 + 2 + j * -3 + 3 + j * -6 + 5 + j * 0 + -7 + j * 6 + 3 + j * -4 + -2 + j * 4 + -4 + j * -2 + 9 + j * -8 + -3 + j * 3 + 0 + j * -6 + 6 + j * 4 + -2 + j * -4 + 9 + j * 0 + -8 + j * -9 + 0 + j * -4 + 7 + j * -4 + 8 + j * 6 + -2 + j * -1 + -8 + j * 6 + 9 + j * -3 + 1 + j * 4 + 3 + j * -3 + 2 + j * -2 + 9 + j * 6 + -8 + j * -7 + -7 + j * 2 + 8 + j * -1 + 1 + j * 0 + -1 + j * -3 + 3 + j * -9 + 1 + j * 9 + -2 + j * 8 + 8 + j * -5 + 6 + j * 4 + 8 + j * 3 + 9 + j * 9 + 3 + j * -9 + 4 + j * -5 + 4 + j * -2 + 8 + j * 2 + -3 + j * 1 + -8 + j * -9 + 4 + j * 6 + 4 + j * 7 + 2 + j * 7 + -4 + j * -6 + 6 + j * 9 + 3 + j * -5 + 6 + j * -7 + -4 + j * -6 + 3 + j * -1 + 5 + j * 8 + 6 + j * -6 + -5 + j * 0 + 2 + j * -2 + -2 + j * -5 + -4 + j * -8 + 9 + j * -5 + 3 + j * 3 + -3 + j * -9 + -5 + j * -7 + 6 + j * -6 + -9 + j * -3 + 1 + j * -9 + 3 + j * -5 + 3 + j * 9 + 8 + j * -9 + -8 + j * 3 + 1 + j * -5 + 9 + j * 4 + -6 + j * 7 + 3 + j * 5 + 9 + j * 9 + -4 + j * -6 + -1 + j * -8 + -6 + j * 3 + 4 + j * -3 + -6 + j * -7 + 2 + j * -3 + -8 + j * 0 + 1 + j * 9 + 9 + j * 0 + -3 + j * 2 + -2 + j * 7 + 8 + j * -9 + 1 + j * 6 + -4 + j * -1 + 5 + j * -1 + 8 + j * 9 + 3 + j * -9 + 8 + j * 3 + -3 + j * -2 ]; + +fft(x(1:8)) +fft(x(1:16)) +fft(x(1:64)) +fft(x) diff --git a/dol/examples/example5/src/consumer.c b/dol/examples/example5/src/consumer.c new file mode 100644 index 0000000..376ecee --- /dev/null +++ b/dol/examples/example5/src/consumer.c @@ -0,0 +1,29 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) +{ + ; //nothing to be done here +} + +int consumer_fire(DOLProcess *p) +{ + CREATEPORTVAR(input_port); + + for (p->local->index = 0; p->local->index < NUMBER_OF_FFT_POINTS; + p->local->index++) { + CREATEPORT(input_port, PORT_OUTPUT_COEFFICIENTS, 1, + p->local->index, NUMBER_OF_FFT_POINTS); + DOL_read((void*)input_port, &(p->local->coeffs[p->local->index]), + sizeof(ComplexNumber), p); + printf("%15s: coeff[%d]: %9f + j * %9f\n", + "output_consumer", p->local->index, + p->local->coeffs[p->local->index].real, + p->local->coeffs[p->local->index].imag); + } + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example5/src/consumer.h b/dol/examples/example5/src/consumer.h new file mode 100644 index 0000000..0daf317 --- /dev/null +++ b/dol/examples/example5/src/consumer.h @@ -0,0 +1,18 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_OUTPUT_COEFFICIENTS "output_coefficients" + +typedef struct _local_states +{ + int index; + ComplexNumber coeffs[NUMBER_OF_FFT_POINTS]; +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example5/src/fft2.c b/dol/examples/example5/src/fft2.c new file mode 100644 index 0000000..ec56b82 --- /dev/null +++ b/dol/examples/example5/src/fft2.c @@ -0,0 +1,75 @@ +#include +#include +#include "fft2.h" + +/** + * Determines the twiddle factor of this 2-point FFT based on the + * indices of the process. + */ +void fft2_init(DOLProcess *p) +{ + const float PI = 2.0 * asin(1.0); + int layer_index = GETINDEX(0); + int process_index = GETINDEX(1); + float exponent = 2.0 * PI * + (float)(process_index % (1 << layer_index)) / + (float)(1 << (layer_index + 1)); + + sprintf(p->local->id, "FFT2_%d_%d", + GETINDEX(0), + GETINDEX(1)); + p->local->twiddle_factor.real = cos(exponent); + p->local->twiddle_factor.imag = -sin(exponent); + + printf("%15s: twiddle_factor %9f + j * %9f\n", + p->local->id, + p->local->twiddle_factor.real, + p->local->twiddle_factor.imag); +} + +/** + * Computes 2-point FFT. + */ +int fft2_fire(DOLProcess *p) +{ + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(ComplexNumber), p); + DOL_read((void*)PORT_INB, &(p->local->inB), sizeof(ComplexNumber), p); + + p->local->rotated_inB.real = p->local->inB.real + * p->local->twiddle_factor.real + - p->local->inB.imag + * p->local->twiddle_factor.imag ; + p->local->rotated_inB.imag = p->local->inB.real + * p->local->twiddle_factor.imag + + p->local->inB.imag + * p->local->twiddle_factor.real; + + p->local->outA.real = p->local->inA.real + + p->local->rotated_inB.real; + p->local->outA.imag = p->local->inA.imag + + p->local->rotated_inB.imag; + + p->local->outB.real = p->local->inA.real + - p->local->rotated_inB.real; + p->local->outB.imag = p->local->inA.imag + - p->local->rotated_inB.imag; + + DOL_write((void*)PORT_OUTA, &(p->local->outA), sizeof(ComplexNumber), p); + DOL_write((void*)PORT_OUTB, &(p->local->outB), sizeof(ComplexNumber), p); + + /* + printf("%15s: ", p->local->id); + printf("%9f + j * %9f, %9f + j * %9f => %9f + j * %9f, %9f + j * %9f\n", + p->local->inA.real, + p->local->inA.imag, + p->local->inB.real, + p->local->inB.imag, + p->local->outA.real, + p->local->outA.imag, + p->local->outB.real, + p->local->outB.imag); + */ + + DOL_detach(p); + return -1; +} diff --git a/dol/examples/example5/src/fft2.h b/dol/examples/example5/src/fft2.h new file mode 100644 index 0000000..79e567e --- /dev/null +++ b/dol/examples/example5/src/fft2.h @@ -0,0 +1,21 @@ +#ifndef FFT2_H +#define FFT2_H + +#include +#include "global.h" + +#define PORT_INA "inA" +#define PORT_INB "inB" +#define PORT_OUTA "outA" +#define PORT_OUTB "outB" + +typedef struct _local_states +{ + char id[14]; + ComplexNumber inA, inB, outA, outB, rotated_inB, twiddle_factor; +} Fft2_State; + +void fft2_init(DOLProcess *); +int fft2_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example5/src/generator.c b/dol/examples/example5/src/generator.c new file mode 100644 index 0000000..9d160cf --- /dev/null +++ b/dol/examples/example5/src/generator.c @@ -0,0 +1,47 @@ +#include + +#include "generator.h" + +/** + * Returns a random integer in the range between lower_bound and + * upper_bound, where the bounding values are included in the interval. + */ +int getRandomNumber(int lower_bound, int upper_bound) +{ + return (rand() % (upper_bound - lower_bound + 1)) + lower_bound; +} + + +void generator_init(DOLProcess *p) +{ + ; //nothing to be done here +} + + +int generator_fire(DOLProcess *p) +{ + CREATEPORTVAR(output_port); + + srand(0); //initialize random number generator + + //generate input coefficients and write them to output ports + for (p->local->index = 0; + p->local->index < NUMBER_OF_FFT_POINTS; + p->local->index++) { + p->local->coeffs[p->local->index].real = (float)getRandomNumber(-9, 9); + p->local->coeffs[p->local->index].imag = (float)getRandomNumber(-9, 9); + + CREATEPORT(output_port, PORT_INPUT_COEFFICIENTS, 1, + p->local->index, NUMBER_OF_FFT_POINTS); + printf("%15s: Write to input_coefficients_%d: %9f + j * %9f\n", + "input_generator", p->local->index, + p->local->coeffs[p->local->index].real, + p->local->coeffs[p->local->index].imag); + DOL_write((void*)output_port, &(p->local->coeffs[p->local->index]), + sizeof(ComplexNumber), p); + } + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example5/src/generator.h b/dol/examples/example5/src/generator.h new file mode 100644 index 0000000..511ec04 --- /dev/null +++ b/dol/examples/example5/src/generator.h @@ -0,0 +1,19 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include +#include "global.h" +#include "stdlib.h" + +#define PORT_INPUT_COEFFICIENTS "input_coefficients" + +typedef struct _local_states +{ + int index; + ComplexNumber coeffs[NUMBER_OF_FFT_POINTS]; +} Generator_State; + +void generator_init(DOLProcess *); +int generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example5/src/global.h b/dol/examples/example5/src/global.h new file mode 100644 index 0000000..b1373b2 --- /dev/null +++ b/dol/examples/example5/src/global.h @@ -0,0 +1,13 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define NUMBER_OF_FFT_POINTS 16 + +//structure for holding complex numbers +typedef struct complex_number +{ + float real; + float imag; +} ComplexNumber; + +#endif diff --git a/dol/examples/example6/example6.xml b/dol/examples/example6/example6.xml new file mode 100644 index 0000000..f8a0d19 --- /dev/null +++ b/dol/examples/example6/example6.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example6/src/consumer.c b/dol/examples/example6/src/consumer.c new file mode 100644 index 0000000..f79ccf9 --- /dev/null +++ b/dol/examples/example6/src/consumer.c @@ -0,0 +1,23 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) +{ + ; //nothing to be done here +} + +int consumer_fire(DOLProcess *p) +{ + static int index; + static float sample; + + for (index = 0; index < 10; index++) + { + DOL_read((void*)PORT_IN, &sample, sizeof(float), p); + printf("%8s: Read sample[%02d]: %+6.4f\n", + "consumer", index, sample); + } + return 0; +} + diff --git a/dol/examples/example6/src/consumer.h b/dol/examples/example6/src/consumer.h new file mode 100644 index 0000000..09d158b --- /dev/null +++ b/dol/examples/example6/src/consumer.h @@ -0,0 +1,15 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include + +#define PORT_IN "in" + +typedef struct _local_states +{ +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example6/src/filter.c b/dol/examples/example6/src/filter.c new file mode 100644 index 0000000..2715647 --- /dev/null +++ b/dol/examples/example6/src/filter.c @@ -0,0 +1,32 @@ +#include +#include +#include "filter.h" + +/** + * Write zero to feedback output. + */ +void filter_init(DOLProcess *p) +{ + p->local->zero = 0.0; + p->local->factor = 0.5; + p->local->firstiteration = 1; +} + +/** + * Filter. + */ +int filter_fire(DOLProcess *p) +{ + if (p->local->firstiteration) { + DOL_write((void*)PORT_OUTB, &(p->local->zero), sizeof(float), p); + p->local->firstiteration = 0; + } + + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(float), p); + DOL_read((void*)PORT_INB, &(p->local->inB), sizeof(float), p); + p->local->out = p->local->inA + p->local->factor * p->local->inB; + DOL_write((void*)PORT_OUTA, &(p->local->out), sizeof(float), p); + DOL_write((void*)PORT_OUTB, &(p->local->out), sizeof(float), p); + + return 0; +} diff --git a/dol/examples/example6/src/filter.h b/dol/examples/example6/src/filter.h new file mode 100644 index 0000000..a068113 --- /dev/null +++ b/dol/examples/example6/src/filter.h @@ -0,0 +1,20 @@ +#ifndef FILTER_H +#define FILTER_H + +#include + +#define PORT_INA "inA" +#define PORT_INB "inB" +#define PORT_OUTA "outA" +#define PORT_OUTB "outB" + +typedef struct _local_states +{ + int firstiteration; + float inA, inB, out, zero, factor; +} Filter_State; + +void filter_init(DOLProcess *); +int filter_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example6/src/producer.c b/dol/examples/example6/src/producer.c new file mode 100644 index 0000000..58e161a --- /dev/null +++ b/dol/examples/example6/src/producer.c @@ -0,0 +1,52 @@ +#include +#include + +#include "producer.h" + +/** + * Returns a random integer in the range between lower_bound and + * upper_bound, where the bounding values are included in the interval. + */ +int getRandomNumber(int lower_bound, int upper_bound) +{ + return (rand() % (upper_bound - lower_bound + 1)) + lower_bound; +} + + +void producer_init(DOLProcess *p) +{ + ; //nothing to be done here +} + + +int producer_fire(DOLProcess *p) +{ + static int index; + + srand(0); //initialize random number generator + + //generate input samples and display them + printf("producer: samples = { "); + + for (index = 0; index < 10; index++) { + p->local->sample[index] = (float) getRandomNumber(-9, 9); + if (index < 9) { + printf("%+3.1f, ", p->local->sample[index]); + } + else { + printf("%+3.1f }\n", p->local->sample[index]); + } + } + + //write samples to output port + for (index = 0; index < 10; index++) { + printf("%8s: Write sample[%02d]: %+6.4f\n", + "producer", index, p->local->sample[index]); + DOL_write((void*)PORT_OUT, &(p->local->sample[index]), + sizeof(float), p); + } + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example6/src/producer.h b/dol/examples/example6/src/producer.h new file mode 100644 index 0000000..9aacfe7 --- /dev/null +++ b/dol/examples/example6/src/producer.h @@ -0,0 +1,17 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include +#include "stdlib.h" + +#define PORT_OUT "out" + +typedef struct _local_states +{ + float sample[10]; +} Producer_State; + +void producer_init(DOLProcess *); +int producer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example7/example7.xml b/dol/examples/example7/example7.xml new file mode 100644 index 0000000..271c3b9 --- /dev/null +++ b/dol/examples/example7/example7.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/example7/filter_script.m b/dol/examples/example7/filter_script.m new file mode 100644 index 0000000..dab0c8a --- /dev/null +++ b/dol/examples/example7/filter_script.m @@ -0,0 +1,15 @@ +clear all; +close all; +clc; + +N = 3; +NUMBER_OF_SAMPLES = 5; +FILTER_COEFFICIENTS = [0.1 -0.1 0.4 1.0 -0.7 0.2 -0.9 -0.4 -0.8]; +x = [-9 4 -2 0 -3 0 8 3 6 0, ... + -7 -9 -5 2 1 2 8 -4 -6 -4]; + +%example 6 +y = filter(1, [1 -0.5], x(1:10)) + +%example 7 +y = filter(1, [1 -FILTER_COEFFICIENTS(1:N-1)], x (1:NUMBER_OF_SAMPLES)) diff --git a/dol/examples/example7/src/consumer.c b/dol/examples/example7/src/consumer.c new file mode 100644 index 0000000..ace9ccc --- /dev/null +++ b/dol/examples/example7/src/consumer.c @@ -0,0 +1,23 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) +{ + printf("init consumer.\n"); +} + +int consumer_fire(DOLProcess *p) +{ + static int index; + static float sample; + + for (index = 0; index < NUMBER_OF_SAMPLES; index++) + { + DOL_read((void*)PORT_IN, &sample, sizeof(float), p); + printf("%8s: Read sample[%02d]: %+6.4f\n", + "consumer", index, sample); + } + return 0; +} + diff --git a/dol/examples/example7/src/consumer.h b/dol/examples/example7/src/consumer.h new file mode 100644 index 0000000..8908e5d --- /dev/null +++ b/dol/examples/example7/src/consumer.h @@ -0,0 +1,16 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_IN "in" + +typedef struct _local_states +{ +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example7/src/filter.c b/dol/examples/example7/src/filter.c new file mode 100644 index 0000000..fd8c3ab --- /dev/null +++ b/dol/examples/example7/src/filter.c @@ -0,0 +1,83 @@ +#include +#include +#include "filter.h" + +/** + * Init. + */ +void filter_init(DOLProcess *p) +{ + int k; + p->local->process_index = GETINDEX(0); + + srand(0); //initialize random number generator + for (k = 0; k < p->local->process_index; k++) + rand(); + + sprintf(p->local->id, "filter_%d", + GETINDEX(0)); + p->local->first_invocation = 1; + //generate a random filter coefficient between -1 and 1 + p->local->filter_coefficient = ((float) (rand() % 21) - 10)/10.0; + p->local->zero = 0.0; + printf("init %s: filter coefficient = %+2.1f\n", + p->local->id, p->local->filter_coefficient); +} + +/** + * Filter. + */ +int filter_fire(DOLProcess *p) +{ + //behaviour of the top filter stage + if (p->local->process_index == 0) { + if (p->local->first_invocation) { + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(float), p); + p->local->out = p->local->inA; + p->local->inB = 0.0; + p->local->first_invocation = 0; + } + else { + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(float), p); + DOL_read((void*)PORT_INB, &(p->local->inB), sizeof(float), p); + p->local->out = p->local->inB + p->local->inA; + } + + DOL_write((void*)PORT_OUTA, &(p->local->out), sizeof(float), p); + DOL_write((void*)PORT_OUTB, &(p->local->out), sizeof(float), p); + + printf("%8s: inA: %6.4f, inB: %6.4f, outA = outB: %6.4f\n", + p->local->id, p->local->inA, p->local->inB, p->local->out); + } + //behaviour of the intermediate filter stages + else { + if (p->local->first_invocation) { + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(float), p); + p->local->out = p->local->filter_coefficient * p->local->inA; + p->local->inB = 0.0; + p->local->first_invocation = 0; + } + else { + DOL_read((void*)PORT_INA, &(p->local->inA), sizeof(float), p); + DOL_read((void*)PORT_INB, &(p->local->inB), sizeof(float), p); + p->local->out = p->local->inB + p->local->filter_coefficient + * p->local->inA; + } + + if (p->local->process_index < N - 1) { + DOL_write((void*)PORT_OUTA, &(p->local->inA), sizeof(float), p); + DOL_write((void*)PORT_OUTB, &(p->local->out), sizeof(float), p); + } + //behaviour of the bottom filter stage + else { + DOL_write((void*)PORT_OUTA, &(p->local->zero), sizeof(float), p); + DOL_write((void*)PORT_OUTB, &(p->local->out), sizeof(float), p); + } + + printf("%8s: inA: %6.4f, inB: %6.4f, outA: %6.4f, outB: %6.4f\n", + p->local->id, p->local->inA, p->local->inB, p->local->inA, + p->local->out); + } + + return 0; +} diff --git a/dol/examples/example7/src/filter.h b/dol/examples/example7/src/filter.h new file mode 100644 index 0000000..8d6b07b --- /dev/null +++ b/dol/examples/example7/src/filter.h @@ -0,0 +1,23 @@ +#ifndef FILTER_H +#define FILTER_H + +#include +#include "global.h" + +#define PORT_INA "inA" +#define PORT_INB "inB" +#define PORT_OUTA "outA" +#define PORT_OUTB "outB" + +typedef struct _local_states +{ + char id[10]; //will contain "filter_xx" + int first_invocation; + int process_index; + float inA, inB, out, filter_coefficient, zero; +} Filter_State; + +void filter_init(DOLProcess *); +int filter_fire(DOLProcess *); + +#endif diff --git a/dol/examples/example7/src/global.h b/dol/examples/example7/src/global.h new file mode 100644 index 0000000..b2931f2 --- /dev/null +++ b/dol/examples/example7/src/global.h @@ -0,0 +1,7 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define N 3 +#define NUMBER_OF_SAMPLES 5 + +#endif diff --git a/dol/examples/example7/src/producer.c b/dol/examples/example7/src/producer.c new file mode 100644 index 0000000..89f2aaa --- /dev/null +++ b/dol/examples/example7/src/producer.c @@ -0,0 +1,50 @@ +#include +#include + +#include "producer.h" + +/** + * Returns a random integer in the range between lower_bound and + * upper_bound, where the bounding values are included in the interval. + */ +int getRandomNumber(int lower_bound, int upper_bound) +{ + return (rand() % (upper_bound - lower_bound + 1)) + lower_bound; +} + + +void producer_init(DOLProcess *p) +{ + printf("init producer.\n"); +} + + +int producer_fire(DOLProcess *p) +{ + srand(0); //initialize random number generator + + //generate input samples and display them + printf("producer: samples = { "); + + for (p->local->index = 0; p->local->index < NUMBER_OF_SAMPLES; p->local->index++) { + p->local->sample[p->local->index] = (float) getRandomNumber(-9, 9); + if (p->local->index < NUMBER_OF_SAMPLES - 1) { + printf("%+3.1f, ", p->local->sample[p->local->index]); + } + else { + printf("%+3.1f }\n", p->local->sample[p->local->index]); + } + } + + //write samples to output port + for (p->local->index = 0; p->local->index < NUMBER_OF_SAMPLES; p->local->index++) { + printf("%8s: Write sample[%02d]: %+6.4f\n", + "producer", p->local->index, p->local->sample[p->local->index]); + DOL_write((void*)PORT_OUT, &(p->local->sample[p->local->index]), + sizeof(float), p); + } + + DOL_detach(p); + return -1; +} + diff --git a/dol/examples/example7/src/producer.h b/dol/examples/example7/src/producer.h new file mode 100644 index 0000000..2cffa95 --- /dev/null +++ b/dol/examples/example7/src/producer.h @@ -0,0 +1,19 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include +#include "global.h" +#include "stdlib.h" + +#define PORT_OUT "out" + +typedef struct _local_states +{ + int index; + float sample[NUMBER_OF_SAMPLES]; +} Producer_State; + +void producer_init(DOLProcess *); +int producer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/examplecell/cell.xml b/dol/examples/examplecell/cell.xml new file mode 100644 index 0000000..c1397f3 --- /dev/null +++ b/dol/examples/examplecell/cell.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/examplecell/examplecell.xml b/dol/examples/examplecell/examplecell.xml new file mode 100644 index 0000000..5e63359 --- /dev/null +++ b/dol/examples/examplecell/examplecell.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/examplecell/mapping.xml b/dol/examples/examplecell/mapping.xml new file mode 100644 index 0000000..aafe1d8 --- /dev/null +++ b/dol/examples/examplecell/mapping.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/examplecell/src/consumer.c b/dol/examples/examplecell/src/consumer.c new file mode 100644 index 0000000..cb89dd0 --- /dev/null +++ b/dol/examples/examplecell/src/consumer.c @@ -0,0 +1,23 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) { + sprintf(p->local->name, "consumer"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int consumer_fire(DOLProcess *p) { + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &p->local->c, sizeof(float), p); + printf("%s: %f\n", p->local->name, p->local->c); + p->local->index++; + } else { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplecell/src/consumer.h b/dol/examples/examplecell/src/consumer.h new file mode 100644 index 0000000..ca0fbba --- /dev/null +++ b/dol/examples/examplecell/src/consumer.h @@ -0,0 +1,19 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_IN 100 + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +typedef struct _local_states { + char name[10]; + int index; + int len; + float c; +} Consumer_State; + +#endif diff --git a/dol/examples/examplecell/src/generator.c b/dol/examples/examplecell/src/generator.c new file mode 100644 index 0000000..237f54e --- /dev/null +++ b/dol/examples/examplecell/src/generator.c @@ -0,0 +1,23 @@ +#include + +#include "generator.h" + +void generator_init(DOLProcess *p) { + sprintf(p->local->name, "generator"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int generator_fire(DOLProcess *p) { + if (p->local->index < p->local->len) { + p->local->x = (float)p->local->index; + DOL_write((void*)PORT_OUT, &p->local->x, sizeof(float), p); + p->local->index++; + } else { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplecell/src/generator.h b/dol/examples/examplecell/src/generator.h new file mode 100644 index 0000000..dbc225f --- /dev/null +++ b/dol/examples/examplecell/src/generator.h @@ -0,0 +1,19 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include +#include "global.h" + +#define PORT_OUT 10 + +void generator_init(DOLProcess *); +int generator_fire(DOLProcess *); + +typedef struct _local_states { + char name[10]; + int index; + int len; + float x; +} Generator_State; + +#endif diff --git a/dol/examples/examplecell/src/global.h b/dol/examples/examplecell/src/global.h new file mode 100644 index 0000000..92dc11c --- /dev/null +++ b/dol/examples/examplecell/src/global.h @@ -0,0 +1,6 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define LENGTH 6 + +#endif diff --git a/dol/examples/examplecell/src/square.c b/dol/examples/examplecell/src/square.c new file mode 100644 index 0000000..c1c6caa --- /dev/null +++ b/dol/examples/examplecell/src/square.c @@ -0,0 +1,24 @@ +#include + +#include "square.h" + +void square_init(DOLProcess *p) { + sprintf(p->local->name, "square"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int square_fire(DOLProcess *p) { + if (p->local->index < p->local->len) { + DOL_read((void*)PORT_IN, &p->local->i, sizeof(float), p); + p->local->i = p->local->i + p->local->i; + DOL_write((void*)PORT_OUT, &p->local->i, sizeof(float), p); + p->local->index++; + } else { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplecell/src/square.h b/dol/examples/examplecell/src/square.h new file mode 100644 index 0000000..5b947a8 --- /dev/null +++ b/dol/examples/examplecell/src/square.h @@ -0,0 +1,20 @@ +#ifndef SQUARE_H +#define SQUARE_H + +#include +#include "global.h" + +#define PORT_IN 0 +#define PORT_OUT 1 + +void square_init(DOLProcess *); +int square_fire(DOLProcess *); + +typedef struct _local_states { + char name[10]; + int index; + int len; + float i; +} Square_State; + +#endif diff --git a/dol/examples/exampleproducerconsumer/exampleproducerconsumer.xml b/dol/examples/exampleproducerconsumer/exampleproducerconsumer.xml new file mode 100644 index 0000000..5ccb2f3 --- /dev/null +++ b/dol/examples/exampleproducerconsumer/exampleproducerconsumer.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/exampleproducerconsumer/src/consumer.c b/dol/examples/exampleproducerconsumer/src/consumer.c new file mode 100644 index 0000000..c39157b --- /dev/null +++ b/dol/examples/exampleproducerconsumer/src/consumer.c @@ -0,0 +1,21 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) { + sprintf(p->local->name, "consumer"); +} + +int consumer_fire(DOLProcess *p) { + char c; + + if (DOL_rtest((void*)PORT_INB, 1, p)) { + DOL_read((void*)PORT_INA, &c, sizeof(char), p); + printf("from port B: %c\n", c); + DOL_read((void*)PORT_INA, &c, sizeof(char), p); + printf("from port A: %c\n", c); + } + + return 0; +} + diff --git a/dol/examples/exampleproducerconsumer/src/consumer.h b/dol/examples/exampleproducerconsumer/src/consumer.h new file mode 100644 index 0000000..47d5ebf --- /dev/null +++ b/dol/examples/exampleproducerconsumer/src/consumer.h @@ -0,0 +1,16 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include + +#define PORT_INA "A" +#define PORT_INB "B" + +typedef struct _local_states { + char name[10]; +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/exampleproducerconsumer/src/producer.c b/dol/examples/exampleproducerconsumer/src/producer.c new file mode 100644 index 0000000..2e0130b --- /dev/null +++ b/dol/examples/exampleproducerconsumer/src/producer.c @@ -0,0 +1,37 @@ +#include +#include + +#include "producer.h" + +// initialization function +void producer_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = 20; + sprintf(p->local->str, "abcdefghijklmnopqrstuvwxyz"); + +} + +int producer_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + if (DOL_wtest((void*)PORT_OUTA, 1, p)) { + DOL_write((void*)PORT_OUTA, &(p->local->str[p->local->index]), + 1, p); + printf("p write to port A %c\n", + p->local->str[p->local->index]); + } + else { + DOL_write((void*)PORT_OUTB, &(p->local->str[p->local->index]), + 1, p); + printf("p write to port B %c\n", + p->local->str[p->local->index]); + } + p->local->index++; + return 0; + } + else { + DOL_detach(p); + return -1; + } +} + diff --git a/dol/examples/exampleproducerconsumer/src/producer.h b/dol/examples/exampleproducerconsumer/src/producer.h new file mode 100644 index 0000000..6668050 --- /dev/null +++ b/dol/examples/exampleproducerconsumer/src/producer.h @@ -0,0 +1,18 @@ +#ifndef PRODUCER_H +#define PRODUCER_H + +#include + +#define PORT_OUTA "A" +#define PORT_OUTB "B" + +typedef struct _local_states { + int index; + int len; + char str[26]; +} Producer_State; + +void producer_init(DOLProcess *); +int producer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/examplesingleprocess/examplesingleprocess.xml b/dol/examples/examplesingleprocess/examplesingleprocess.xml new file mode 100644 index 0000000..07937ae --- /dev/null +++ b/dol/examples/examplesingleprocess/examplesingleprocess.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/dol/examples/examplesingleprocess/src/task.c b/dol/examples/examplesingleprocess/src/task.c new file mode 100644 index 0000000..9544c0b --- /dev/null +++ b/dol/examples/examplesingleprocess/src/task.c @@ -0,0 +1,21 @@ +#include + +#include "task.h" + +void task_init(DOLProcess *p) { + sprintf(p->local->name, "task_%d", GETINDEX(0)); + p->local->index = 0; +} + +int task_fire(DOLProcess *p) { + if (p->local->index < 10) { + printf("%s: %d\n", p->local->name, p->local->index++); + } + + if (p->local->index >= 10) { + DOL_detach(p); + } + + return 0; +} + diff --git a/dol/examples/examplesingleprocess/src/task.h b/dol/examples/examplesingleprocess/src/task.h new file mode 100644 index 0000000..c68d499 --- /dev/null +++ b/dol/examples/examplesingleprocess/src/task.h @@ -0,0 +1,15 @@ +#ifndef TASK_H +#define TASK_H + +#include + +typedef struct _local_states { + char name[10]; + int index; + int len; +} Task_State; + +void task_init(DOLProcess *); +int task_fire(DOLProcess *); + +#endif diff --git a/dol/examples/examplewindowedfifo/examplewindowedfifo.xml b/dol/examples/examplewindowedfifo/examplewindowedfifo.xml new file mode 100644 index 0000000..a09d69b --- /dev/null +++ b/dol/examples/examplewindowedfifo/examplewindowedfifo.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/examplewindowedfifo/src/consumer.c b/dol/examples/examplewindowedfifo/src/consumer.c new file mode 100644 index 0000000..cf6902a --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/consumer.c @@ -0,0 +1,27 @@ +#include + +#include "consumer.h" + +void consumer_init(DOLProcess *p) { + sprintf(p->local->name, "consumer"); + p->local->index = 0; + p->local->len = LENGTH; +} + +int consumer_fire(DOLProcess *p) { + float *c; + if (p->local->index < p->local->len) { + DOL_capture((void*)PORT_IN, &c, sizeof(float), p); + printf("%s: %f\n", p->local->name, *c); + DOL_consume((void*)PORT_IN, p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplewindowedfifo/src/consumer.h b/dol/examples/examplewindowedfifo/src/consumer.h new file mode 100644 index 0000000..677609a --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/consumer.h @@ -0,0 +1,18 @@ +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include "global.h" + +#define PORT_IN 1 + +typedef struct _local_states { + char name[10]; + int index; + int len; +} Consumer_State; + +void consumer_init(DOLProcess *); +int consumer_fire(DOLProcess *); + +#endif diff --git a/dol/examples/examplewindowedfifo/src/generator.c b/dol/examples/examplewindowedfifo/src/generator.c new file mode 100644 index 0000000..943294d --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/generator.c @@ -0,0 +1,29 @@ +#include +#include + +#include "generator.h" + +// initialization function +void generator_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int generator_fire(DOLProcess *p) { + + if (p->local->index < p->local->len) { + float *x; + DOL_reserve((void*)PORT_OUT, &x, sizeof(float), p); + *x = (float)p->local->index; + DOL_release((void*)PORT_OUT, p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplewindowedfifo/src/generator.h b/dol/examples/examplewindowedfifo/src/generator.h new file mode 100644 index 0000000..b938a38 --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/generator.h @@ -0,0 +1,17 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include +#include "global.h" + +#define PORT_OUT 1 + +typedef struct _local_states { + int index; + int len; +} Generator_State; + +void generator_init(DOLProcess *); +int generator_fire(DOLProcess *); + +#endif diff --git a/dol/examples/examplewindowedfifo/src/global.h b/dol/examples/examplewindowedfifo/src/global.h new file mode 100644 index 0000000..dfcbb84 --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/global.h @@ -0,0 +1,6 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define LENGTH 20 + +#endif diff --git a/dol/examples/examplewindowedfifo/src/square.c b/dol/examples/examplewindowedfifo/src/square.c new file mode 100644 index 0000000..7e0b5d2 --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/square.c @@ -0,0 +1,29 @@ +#include + +#include "square.h" + +void square_init(DOLProcess *p) { + p->local->index = 0; + p->local->len = LENGTH; +} + +int square_fire(DOLProcess *p) { + float *i, *j; + + if (p->local->index < p->local->len) { + DOL_capture((void*)PORT_IN, &i, sizeof(float), p); + DOL_reserve((void*)PORT_OUT, &j, sizeof(float), p); + *j = *i * *i; + DOL_consume((void*)PORT_IN, p); + DOL_release((void*)PORT_OUT, p); + p->local->index++; + } + + if (p->local->index >= p->local->len) { + DOL_detach(p); + return -1; + } + + return 0; +} + diff --git a/dol/examples/examplewindowedfifo/src/square.h b/dol/examples/examplewindowedfifo/src/square.h new file mode 100644 index 0000000..e2eebd0 --- /dev/null +++ b/dol/examples/examplewindowedfifo/src/square.h @@ -0,0 +1,18 @@ +#ifndef SQUARE_H +#define SQUARE_H + +#include +#include "global.h" + +#define PORT_IN 100 +#define PORT_OUT 200 + +typedef struct _local_states { + int index; + int len; +} Square_State; + +void square_init(DOLProcess *); +int square_fire(DOLProcess *); + +#endif diff --git a/dol/examples/runexample.xml b/dol/examples/runexample.xml new file mode 100644 index 0000000..fa03bfe --- /dev/null +++ b/dol/examples/runexample.xml @@ -0,0 +1,285 @@ + + + + + + Ant build file to build and run examples. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/runprofiler.xml b/dol/examples/runprofiler.xml new file mode 100644 index 0000000..a3e4ade --- /dev/null +++ b/dol/examples/runprofiler.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/schema/architecture.xsd b/dol/examples/schema/architecture.xsd new file mode 100644 index 0000000..ddb3871 --- /dev/null +++ b/dol/examples/schema/architecture.xsd @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/schema/createschemastex b/dol/examples/schema/createschemastex new file mode 100644 index 0000000..9b7fe4a --- /dev/null +++ b/dol/examples/schema/createschemastex @@ -0,0 +1,91 @@ +#!/bin/bash + +########################################################################## +# Script to collect together processnetwork.xsd, architecture.xsd, and +# mapping.xsd into a single LaTeX document. +# +# Lines longer than 80 characters in the source files are truncated. +# +# modification history: +# 2006-08-22: created +# +########################################################################## + +########################################################################## +# create LaTeX header +########################################################################## + +echo -e %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +% XML Schema Definitions.'\n'\ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +'\n'\ +'\\'documentclass[11pt,oneside]{book}'\n'\ +'\\'usepackage{fancyhdr,ifthen,a4wide,textcomp,version,amsmath,bbm,url}'\n'\ +'\\'usepackage[latin1]{inputenc}'\n'\ +'\n'\ +%package for including listings'\n'\ +'\\'usepackage{listings}'\n'\ +'\\'lstset{basicstyle='\\'small, xleftmargin=12pt, numbersep=12pt, numbers=left,'\n'\ + numberstyle='\\'tiny, numbersep=5pt}'\n'\ +%print two-digit line numbers'\n'\ +'\\'newcommand{'\\'twodig}[1]'\n'\ + {'\\'ifcase#1 00'\\'or01'\\'or02'\\'or03'\\'or04'\\'or05'\\'or06'\\'or07'\\'or08'\\'or09'\\'else#1'\\'fi}'\n'\ +'\\'renewcommand{'\\'thelstnumber}{'\\'twodig{'\\'arabic{lstnumber}}}'\n'\ +'\n'\ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +%page layout and graphics path'\n'\ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +'\\'setlength{'\\'textwidth}{18 true cm}'\n'\ +'\\'setlength{'\\'textheight}{27.4 true cm}'\n'\ +'\\'oddsidemargin -1.0 cm'\n'\ +'\\'evensidemargin -1.0 cm'\n'\ +'\\'topmargin -2.4 cm'\n'\ +'\\'setlength{'\\'parindent}{0pt}'\n'\ +'\\'sloppy'\n'\ +'\\'flushbottom'\n'\ +'\\'pagestyle{empty}'\n'\ +'\n'\ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'\n'\ +'\n'\ +'\\'begin{document} > latex_header.tex + + +########################################################################## +# create LaTeX footer +########################################################################## + +echo -e '\\'end{document} > latex_footer.tex + + +########################################################################## +# create listings header and footer +########################################################################## + +echo -e '\\'newpage'\n'\ +'\\'begin{lstlisting} > listing_header.tex + +echo -e '\\'end{lstlisting} > listing_footer.tex + + +########################################################################## +# create document +########################################################################## + +cat latex_header.tex listing_header.tex processnetwork.xsd listing_footer.tex \ + listing_header.tex architecture.xsd listing_footer.tex \ + listing_header.tex mapping.xsd listing_footer.tex latex_footer.tex > schemas.tex + + +########################################################################## +# truncate lines longer than 80 characters +########################################################################## + +sed -i 's/\(^.\{80\}\).*/\1.../g' schemas.tex + + +########################################################################## +# remove help files +########################################################################## + +rm -f latex_header.tex latex_footer.tex listing_header.tex listing_footer.tex \ No newline at end of file diff --git a/dol/examples/schema/generics.xsd b/dol/examples/schema/generics.xsd new file mode 100644 index 0000000..28b9108 --- /dev/null +++ b/dol/examples/schema/generics.xsd @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/schema/internal/architecture_internal.xsd b/dol/examples/schema/internal/architecture_internal.xsd new file mode 100644 index 0000000..e76fe32 --- /dev/null +++ b/dol/examples/schema/internal/architecture_internal.xsd @@ -0,0 +1,4 @@ + + + + diff --git a/dol/examples/schema/internal/mapping_internal.xsd b/dol/examples/schema/internal/mapping_internal.xsd new file mode 100644 index 0000000..d0cad50 --- /dev/null +++ b/dol/examples/schema/internal/mapping_internal.xsd @@ -0,0 +1,4 @@ + + + + diff --git a/dol/examples/schema/internal/processnetwork_internal.xsd b/dol/examples/schema/internal/processnetwork_internal.xsd new file mode 100644 index 0000000..0e2d87c --- /dev/null +++ b/dol/examples/schema/internal/processnetwork_internal.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/schema/mapping.xsd b/dol/examples/schema/mapping.xsd new file mode 100644 index 0000000..dbaef38 --- /dev/null +++ b/dol/examples/schema/mapping.xsd @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/examples/schema/processnetwork.xsd b/dol/examples/schema/processnetwork.xsd new file mode 100644 index 0000000..d3c2d93 --- /dev/null +++ b/dol/examples/schema/processnetwork.xsd @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/jars/jdom.jar b/dol/jars/jdom.jar new file mode 100644 index 0000000000000000000000000000000000000000..288e64cb5c435f34499a58b234c2106f9d9f0783 GIT binary patch literal 153253 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

_4s+#N=7*_ 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�s*dMq>JLX-P3 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 z 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 Y`CYb)y=1%oI@n19M~1g#Z8m literal 0 HcmV?d00001 diff --git a/dol/jars/xercesImpl.jar b/dol/jars/xercesImpl.jar new file mode 100644 index 0000000000000000000000000000000000000000..33990e8a1fdf24e15701e4cf8d9f091038394ecd GIT binary patch literal 1207073 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

v5H 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 +1eF>wd?fi> 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%0A3aBXg)x6CNlw(xA= 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�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

3l?%YFr`Fo%7JtujOK6PDUo9bLC2!9j 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

K~0dAveEwdzzf}Bgk&(kgSyi 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 z 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 z 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�c6|%I{92ta8M=~87SLRGaDR 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Ξ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 literal 0 HcmV?d00001 diff --git a/dol/src/MANIFEST.MF b/dol/src/MANIFEST.MF new file mode 100644 index 0000000..19165c3 --- /dev/null +++ b/dol/src/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Main-Class: dol.main.Main +Created-By: Computer Engineering Group, Computer Engineering and Networks Laboratory, ETH Zurich +Class-Path: jars/xercesImpl.jar jars/jdom.jar \ No newline at end of file diff --git a/dol/src/docs/doxygen/doxygen.cfg b/dol/src/docs/doxygen/doxygen.cfg new file mode 100644 index 0000000..7e541a1 --- /dev/null +++ b/dol/src/docs/doxygen/doxygen.cfg @@ -0,0 +1,1078 @@ +# Doxyfile 1.3.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = DOL + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1.0 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ../../../build/doxygen/ + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = ../../src + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = YES + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../../dol + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc + +FILE_PATTERNS = *.java + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 1 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +#HTML_HEADER = header.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +#HTML_FOOTER = footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output dir. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similiar to the OMG's Unified Modeling +# Language. + +UML_LOOK = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = /usr/local/bin/dot + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 600 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 600 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES diff --git a/dol/src/docs/doxygen/footer.html b/dol/src/docs/doxygen/footer.html new file mode 100644 index 0000000..20d26b2 --- /dev/null +++ b/dol/src/docs/doxygen/footer.html @@ -0,0 +1,3 @@ +


+

Generated on $datetime for $projectname by doxygen $doxygenversion

+ diff --git a/dol/src/docs/doxygen/header.html b/dol/src/docs/doxygen/header.html new file mode 100644 index 0000000..6efcc86 --- /dev/null +++ b/dol/src/docs/doxygen/header.html @@ -0,0 +1 @@ + diff --git a/dol/src/dol.properties b/dol/src/dol.properties new file mode 100644 index 0000000..56d50ac --- /dev/null +++ b/dol/src/dol.properties @@ -0,0 +1,15 @@ +# template for dol.properties file +# use ant config to fill in the values + +# SystemC library path: +# SYSTEMC_INC and SYSTEMC_LIB are the paths to SystemC header and library, +# respectively. These two path will be used in the generated Makefile to +# build the SystemC executable binary file. They have to point to local +# SystemC directory. +# +# example: +# SYSTEMC_INC = /home/shapes/base/resources/lib/systemC/systemc-2.1.v1/include +# SYSTEMC_LIB = /home/shapes/base/resources/lib/systemC/systemc-2.1.v1/lib-linux/libsystemc.a + +SYSTEMC_INC = /home/shapes/base/resources/lib/systemC/include +SYSTEMC_LIB = /home/shapes/base/resources/lib/systemC/lib-linux/libsystemc.a diff --git a/dol/src/dol/check/SanityCheck.java b/dol/src/dol/check/SanityCheck.java new file mode 100644 index 0000000..88f82db --- /dev/null +++ b/dol/src/dol/check/SanityCheck.java @@ -0,0 +1,419 @@ +/* $Id: SanityCheck.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.check; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Vector; + +import dol.datamodel.architecture.ArchiResource; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Configuration; +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Connection; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.Resource; + +/** + * Check semantic correctness of process network. + * + * This class checks + *
  • whether it is a processnetwork
  • + *
  • a channel only has two ports
  • + *
  • each channel port has only one peer port of process
  • + *
+ */ +public class SanityCheck { + /** + * Return the singleton instance of this class; + * + * @return the instance. + */ + public final static SanityCheck getInstance() { + return _instance; + } + + /** + * Check if the process network spec is correct + * + * @param pn processnetwork to check + */ + public void checkPN(ProcessNetwork pn) { + try { + _checkNameConflict(pn); + _checkChannelPorts(pn); + _checkChannelConnection(pn); + } + catch (Exception e) { + System.out.println("err: " + e.getMessage()); + //e.printStackTrace(); + System.exit(-1); + } + + try { + _checkProcessConnection(pn); + } + catch (Exception e) { + System.out.println("warning: " + e.getMessage()); + } + } + + /** + * Check if the architecture spec is correct + * + * @param arch architecture to check + */ + public void checkArch(Architecture arch) + { + try + { + _checkNameConflict(arch); + _checkNETSIM(arch); + _checkMaster(arch); + } + catch (Exception e) + { + System.out.println("err: " + e.getMessage()); + System.exit(-1); + } + } + + /** + * Check if the mapping spec is correct + * + * @param map mapping to check + */ + public void checkMap(Mapping map) + { + try + { + _checkMultibind(map); + _checkProcessesBound(map); + } + catch (Exception e) + { + System.out.println("err: " + e.getMessage()); + System.exit(-1); + } + } + + + /** + * Check name exclusiveness, including processes, channels, and + * connections. + * + * @param pn processnetwork to check + */ + private void _checkNameConflict(ProcessNetwork pn) throws Exception { + System.out.println("APPL: Checking resource name ..."); + + Vector n = new Vector(pn.getChannelList()); + n.addAll(pn.getProcessList()); + n.addAll(pn.getConnectionList()); + + //sort resources in n by name + Collections.sort(n, + new Comparator() + { + public int compare(Resource resource1, Resource resource2) { + return resource1.getName().compareTo(resource2.getName()); + } + } ); + + //test adjacent resources in n for equal name + for (int k = 0; k < n.size() - 1; k++) + { + if (((Resource) n.elementAt(k)).getName().equals( + ((Resource) n.elementAt(k + 1)).getName())) { + throw new Exception("Name conflict: " + + ((Resource)n.elementAt(k + 1)).getName() + + " appears (at least) twice."); + } + } + } + + /** + * Check number of ports for every channel. + * + * @param pn processnetwork to check + */ + private void _checkChannelPorts(ProcessNetwork pn) throws Exception { + System.out.println("APPL: Checking channel ports ..."); + + for (Channel c : pn.getChannelList()) { + if (c.getPortList().size() < 2) { + throw new Exception("channel ports less than 2: " + + c.getName()); + } + } + } + + /** + * Check whether all processes are connected. + * + * @param pn processnetwork to check + */ + private void _checkProcessConnection(ProcessNetwork pn) + throws Exception { + System.out.println("APPL: Checking Process connection ..."); + + String result = ""; + boolean hasUnused = false; + for (Process r : pn.getProcessList()) { + boolean flag = false; + for (Connection c : pn.getConnectionList()) { + Resource origin = c.getOrigin(); + Resource target = c.getTarget(); + if (r.getName().equals(origin.getName())) { + flag = true; break; + } else if (r.getName().equals(target.getName())) { + flag = true; break; + } + } + if (flag == false) { + hasUnused = true; + result += " " + r.getName(); + } + } + + if (hasUnused == true) + throw new Exception("process(es) without connection to channels:" + result); + } + + /** + * Check whether there are unused channels in the processnetwork. + * + * @param pn processnetwork to check + */ + private void _checkChannelConnection(ProcessNetwork pn) + throws Exception { + System.out.println("APPL: Checking channel connection ..."); + + String result = ""; + boolean hasUnused = false; + for (Channel r : pn.getChannelList()) { + boolean flag = false; + for (Connection c: pn.getConnectionList()) { + Resource origin = c.getOrigin(); + Resource target = c.getTarget(); + if (r.getName().equals(origin.getName())) { + flag = true; break; + } else if (r.getName().equals(target.getName())) { + flag = true; break; + } + } + if (flag == false) { + hasUnused = true; + result += " " + r.getName(); + } + } + + if (hasUnused == true) + throw new Exception("unused channel: " + result); + } + + + /** + * Check name exclusiveness, including both processors and memories. + * + * @param arch architecture to check + */ + private void _checkNameConflict(Architecture arch) throws Exception { + System.out.println("ARCH: Checking resource name ..."); + Vector n = new Vector(arch.getProcessorList()); + n.addAll(arch.getMemoryList()); + + //sort resources in n by name + Collections.sort(n, + new Comparator() + { + public int compare(ArchiResource resource1, ArchiResource resource2) { + return resource1.getName().compareTo(resource2.getName()); + } + } ); + + //test adjacent resources in n for equal name + for (int k = 0; k < n.size() - 1; k++) + { + if (((ArchiResource) n.elementAt(k)).getName().equals( + ((ArchiResource) n.elementAt(k + 1)).getName())) { + throw new Exception("Name conflict: " + + ((ArchiResource)n.elementAt(k + 1)).getName() + + " appears (at least) twice."); + } + } + } + + /** + * every processor of type NETSIM must have a configuration tag with + * name "address" and one with name "port". these address-port pairs + * have to be unique + * @param arch + * @throws Exception + */ + private void _checkNETSIM(Architecture arch) throws Exception + { + System.out.println("ARCH: Checking network simulators ..."); + + Vector sockets = new Vector(); + + for (Processor proc : arch.getProcessorList()) { + if ( proc.getType().equals("NETSIM") ) + { + if ( proc.getCfg("address") == null + || proc.getCfg("port") == null ) + { + throw new Exception("Processor " + proc.getName() + + " is of type NETSIM but address or port is missing"); + } + if ( proc.getCfg("address").equals("") + || proc.getCfg("port").equals("") ) + { + throw new Exception("Processor " + proc.getName() + + " has empty address or port"); + } + sockets.add(proc); // add processor to socket list + } + } + + /* socket comperator */ + Comparator sockComp = new Comparator() + { + public int compare(Processor proc1, Processor proc2) + { + String ad1, ad2; + ad1 = proc1.getCfg("address").getValue(); + ad2 = proc2.getCfg("address").getValue(); + + int comp = ad1.compareToIgnoreCase(ad2); + + if (comp != 0) + { + return comp; + } + else + { + String p1, p2; + p1 = proc1.getCfg("port").getValue(); + p2 = proc2.getCfg("port").getValue(); + + return p1.compareToIgnoreCase(p2); + } + } + }; + + + /* sort sockets */ + Collections.sort(sockets, sockComp); + + /* check for uniqueness */ + Processor proc1 = null; + Processor proc2 = null; + if (!sockets.isEmpty()) + proc1 = sockets.get(0); + for (int i=1; i processes = map.getProcessList(); + + //sort processes in n by name + Collections.sort(processes, + new Comparator() + { + public int compare(Process p1, Process p2) { + return p1.getName().compareTo(p2.getName()); + } + } ); + + //test adjacent origins for equal name + String o1 = null; + String o2 = null; + if (!processes.isEmpty()) + o1 = processes.get(0).getName(); + for (int k = 1; k < processes.size(); k++) + { + o2 = processes.get(k).getName(); + if (o1.equals(o2)) + { + throw new Exception("sw resource \"" + o1 + + "\" has multiple bindings"); + } + } + } + + /** + * Checks that every process is bound to a processor. + * @param map the map to check + * @throws Exception if not all processes are bound to a processor + */ + private void _checkProcessesBound(Mapping map) throws Exception + { + System.out.println("MAP: Checking that all processes have a binding ..."); + Vector boundList = map.getProcessList(); + Vector totalList = map.getPN().getProcessList(); + + if (boundList.size() != totalList.size()) + { + throw new Exception("some processes are not bound to a processor"); + } + } + + /** + * singleton instance + */ + private final static SanityCheck _instance = new SanityCheck(); +} diff --git a/dol/src/dol/check/package.html b/dol/src/dol/check/package.html new file mode 100644 index 0000000..685bef4 --- /dev/null +++ b/dol/src/dol/check/package.html @@ -0,0 +1,20 @@ + + + + + + +Consistency checker for DOL spcific XML files. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/datamodel/XmlTag.java b/dol/src/dol/datamodel/XmlTag.java new file mode 100644 index 0000000..bb87156 --- /dev/null +++ b/dol/src/dol/datamodel/XmlTag.java @@ -0,0 +1,76 @@ +/* $Id: XmlTag.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.datamodel; + + +/** + * Class to store XML tag for xml element + */ +public class XmlTag { + + /** + * Get a single instance of the XmlTag object. + */ + private final static XmlTag _instance = new XmlTag(); + + /** + * Constructor. Private since only a single version may exist. + */ + private XmlTag() {} + + /** + * returns the singleton instance of this class. + * + * @return The instance value + */ + public final static XmlTag getInstance() { return _instance; } + + // processnetwork XML tag + public final String getPNTag() { return "processnetwork";} + public final String getVariableTag() { return "variable";} + public final String getProcessTag() { return "process";} + public final String getSWChannelTag() { return "sw_channel";} + public final String getPortTag() { return "port";} + public final String getSourceTag() { return "source";} + public final String getProfilingTag() { return "profiling";} + + // architecture XML tag + public final String getArchiTag() { return "architecture";} + public final String getProcessorTag() { return "processor";} + public final String getMemoryTag() { return "memory";} + public final String getNodeTag() { return "node";} + public final String getInPortTag() { return "inputport";} + public final String getOutPortTag() { return "outputport";} + public final String getDuplexPortTag() { return "duplexport";} + public final String getHWChannelTag() { return "hw_channel";} + public final String getReadPathTag() { return "readpath";} + public final String getWritePathTag() { return "writepath";} + public final String getTXBufTag() { return "txbuf";} + public final String getRXBufTag() { return "rxbuf";} + public final String getCHBufTag() { return "chbuf";} + + // mapping XML tag + public final String getMappingTag() { return "mapping";} + public final String getBindingTag() { return "binding";} + public final String getScheduleTag() { return "schedule";} + public final String getResourceTag() { return "resource";} + + // common tag + public final String getConfigurationTag() { return "configuration";} + + //iterator related XML tag + public final String getIteratorTag() { return "iterator";} + public final String getFunctionTag() { return "function";} + public final String getAppendTag() { return "append";} + + // connection tag + public final String getConnectionTag() { return "connection";} + public final String getOriginTag() { return "origin";} + public final String getTargetTag() { return "target";} + + // processnetwork profiling value + public final String getProfilingTotalReadData() { return "TotalReadData";} + public final String getProfilingNumOfReads() { return "NumOfReads";} + public final String getProfilingNumOfWrites() { return "NumOfWrites";} + public final String getProfilingNumOfFires() { return "NumOfFires";} +} + diff --git a/dol/src/dol/datamodel/architecture/ArchiConnection.java b/dol/src/dol/datamodel/architecture/ArchiConnection.java new file mode 100644 index 0000000..9e2d713 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/ArchiConnection.java @@ -0,0 +1,80 @@ +/* $Id: ArchiConnection.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import dol.visitor.ArchiVisitor; + +/** + * This class defines an architecture connection. Used in the simplified + * architecture specification. An architecture connection contains one + * origin and one target resource. + */ +public class ArchiConnection extends ArchiResource { + /** + * Constructor to create an architecture connection with a name. + * + */ + public ArchiConnection(String name) { + super(name); + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this architectural connection. + * + * @return a new instance of the architectural connection. + */ + public Object clone() { + ArchiConnection newObj = (ArchiConnection) super.clone(); + newObj.setOrigin(_origin); + newObj.setTarget(_target); + return (newObj); + } + + /** + * Return a string representation of the architectural connection. + * + * @return string representation of the architectural connection + */ + public String toString() { + return "ArchiConnection: " + getName(); + } + + /** + * Return the origin of the architectural connection. + * + * @return origin architectural resource of the architectural connection + */ + public ArchiResource getOrigin() { return _origin; } + + /** + * Set the origin of the architectural connection. + * + * @param origin architectural resource. + */ + public void setOrigin(ArchiResource origin) { _origin = origin; } + + /** + * Return the target of the architectural connection. + * + * @return target architectural resource of the architectural connection + */ + public ArchiResource getTarget() { return _target; } + + /** + * Set the target of the architectural connection. + * + * @param target architectural resource. + */ + public void setTarget(ArchiResource target) { _target = target; } + + protected ArchiResource _origin; + protected ArchiResource _target; +} diff --git a/dol/src/dol/datamodel/architecture/ArchiResource.java b/dol/src/dol/datamodel/architecture/ArchiResource.java new file mode 100644 index 0000000..04de406 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/ArchiResource.java @@ -0,0 +1,254 @@ +/* $Id: ArchiResource.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class is the basic class which abstracts an architectural resource + * of a generic architecture. The architectural resource has a name and a + * list of included nodes. + */ +public class ArchiResource implements Cloneable { + /** + * Constructor to create an architectural resource with a name and + * an empty node list. + */ + public ArchiResource(String name) { + _name = name; + _basename = name; + _nodeList = new Vector(); + _cfgList = new Vector(); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this architectural resource. + * + * @return new instance of the architectural resource. + */ + @SuppressWarnings("unchecked") + public Object clone() { + try { + ArchiResource newObj = (ArchiResource) super.clone(); + newObj.setName(_name); + newObj.setBasename(_basename); + newObj.setNodeList((Vector)_nodeList.clone() ); + newObj.setCfgList((Vector)_cfgList.clone() ); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the name of this architectural resource. + * + * @return name of the architectural resource + */ + public String getName() { + return _name; + } + + /** + * Set the name of this architectural resource. + * + * @param name name of the architectural resource + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the basename of this architectural resource. + * + * @return basename of the architectural resource + */ + public String getBasename() { + return _basename; + } + + /** + * Set the basename of this architectural resource. + * + * @param basename name of the architectural resource + */ + public void setBasename(String basename) { + _basename = basename; + } + + /** + * Get the iterator indices of this process. + * + * @return range + */ + public Vector getIteratorIndices() { + Vector indices = new Vector(); + StringTokenizer tokenizer = + new StringTokenizer(_name.replaceAll(_basename, ""), "_"); + while (tokenizer.hasMoreTokens()) { + indices.add(Integer.valueOf(tokenizer.nextToken())); + } + return indices; + } + + /** + * Get the list of nodes of this architectural resource. + * + * @return list of nodes + */ + public Vector getNodeList() { + return _nodeList; + } + + /** + * Set the list of nodes of this architectural resource. + * + * @param nodeList nodes list + */ + public void setNodeList(Vector nodeList) { + _nodeList = nodeList; + } + + /** + * Get the hierarchical parent of this architectural resource. + * + * @return parent of this architectural resource + */ + public ArchiResource getParentResource() { + return _parentResource; + } + + /** + * Set the hierarchical parent of this architectural resource. + * + * @param parentResource new parent + */ + public void setParentResource(ArchiResource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the architectural resource. + * + * @return string representation of the architectural resource + */ + public String toString() { + return "ArchiResource: " + _name; + } + + /** + * Return a node (which has a specific name). Return null when node + * cannot be found. + * + * @param name of the node to search for + * @return node with the specified name + */ + public Node getNode(String name) { + Iterator i; + i = _nodeList.iterator(); + while (i.hasNext()) { + Node node = i.next(); + if (node.getName().equals(name)) { + return node; + } + } + return null; + } + + /** + * Return a node. Return null when node cannot be found. + * + * @return node + */ + public Node getNode() { + Iterator i; + i = _nodeList.iterator(); + while (i.hasNext()) { + Node node = i.next(); + return node; + } + return null; + } + + /** + * Has this resource nodes? + * + * @return boolean value + */ + public boolean hasNodes() { + return !_nodeList.isEmpty(); + } + + + /** + * Return the first node of the nodelist. + * + * @return the first node of the nodelist. + */ + public Node getFirstNode() { + return (Node) _nodeList.firstElement(); + } + + /** + * Get the list of configurations of this resource. + * + * @return list of configurations + */ + public Vector getCfgList() { + return _cfgList; + } + + /** + * Set the list of configurations of this resource. + * + * @param cfgList configuration list + */ + public void setCfgList(Vector cfgList) { + _cfgList = cfgList; + } + + /** + * Return a configuration which has a specific name. Return + * null when configuration cannot be found. + * + * @param name name of the configuration to search for + * @return configuration with the specified name + */ + public Configuration getCfg(String name) { + for (Configuration config : _cfgList) { + if (config.getName().equals(name)) { + return config; + } + } + return null; + } + + + /** list of the configurations of the ArchiResource */ + protected Vector _cfgList = null; + + /** name of the architectural resource */ + protected String _name = null; + + /** basename of the architectural resource, if no basename, store the name */ + protected String _basename = null; + + /** list of the nodes, paths of the architectural resource */ + protected Vector _nodeList = null; + + /** parent resource of this architectural resource */ + protected ArchiResource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/architecture/Architecture.java b/dol/src/dol/datamodel/architecture/Architecture.java new file mode 100644 index 0000000..06d510d --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Architecture.java @@ -0,0 +1,400 @@ +/* $Id: Architecture.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Hashtable; +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + + +/** + * This class defines an architecture. + */ +public class Architecture extends ArchiResource { + /** + * Constructor to create an architecture with a name, + * empty processor list, empty memory list, empty hwChannel list and empty connection list. + */ + public Architecture(String name) { + super(name); + _processorList = new Vector(); + _memoryList = new Vector(); + _hwChannelList = new Vector(); + _varList = new Vector(); + _connectionList = new Vector(); + _readPathList = new Vector(); + _writePathList = new Vector(); + _pathList = new Vector(); + _processorPathList = new Hashtable>>(); + + } + + /** + * Accept a visitor + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Architecture. + * + * @return new instance of the Architecure + */ + /* + @SuppressWarnings("unchecked") + public Object clone() { + Architecture newObj = (Architecture) super.clone(); + newObj.setProcessorList((Vector)_processorList.clone()); + newObj.setMemoryList((Vector)_memoryList.clone()); + newObj.setHWChannelList((Vector)_hwChannelList.clone()); + newObj.setVarList((Vector)_varList.clone()); + newObj.setConnectionList((Vector)_connectionList.clone()); + newObj.setReadPathList((Vector)_readPathList.clone()); + newObj.setWritePathList((Vector)_writePathList.clone()); + newObj.setPathList((Vector>)_pathList.clone()); + return newObj; + } + */ + + + /** + * Get the processor list of a Architecture. + * + * @return the processor list + */ + public Vector getProcessorList() { + return _processorList; + } + + /** + * Get the memory list of a Architecture. + * + * @return the memory list + */ + public Vector getMemoryList() { + return _memoryList; + } + + /** + * Get the hwChannel list of a Architecture. + * + * @return the hwChannel list + */ + public Vector getHWChannelList() { + return _hwChannelList; + } + + /** + * Get the paths list of a Architecture. + * + * @return the paths list + */ + public Vector getReadPathList() { + return _readPathList; + } + + /** + * Get the write paths list of a Architecture. + * + * @return the paths list + */ + public Vector getWritePathList( ) { + return _writePathList; + } + + /** + * Set the processor paths list of a Architecture. + * + * @param pathList The new list + */ + public void setPathList(Vector pathList) { + _pathList = pathList; + } + + /** + * Get the processor paths list of a Architecture. + * + * @return the processor paths list + */ + public Vector getPathList() { + return _pathList; + } + + /** + * Return a processor/memory/hwChannel which has a specific name. Return null if + * processor cannot be found. + * + * @param name the name of the processor/memory/hwChannel to search for. + * @return the processor/memory/hwChannel with the specific name. + */ + public Processor getProcessor(String name) { + for (Processor processor : _processorList) { + if (processor.getName().equals(name)) { + return processor; + } + } + return null; + } + + /** + * + */ + public Memory getMemory(String name) { + for (Memory memory : _memoryList) { + if (memory.getName().equals(name)) { + return memory; + } + } + return null; + } + + /** + * + */ + public HWChannel getHWChannel(String name) { + for (HWChannel hwChannel : _hwChannelList) { + if (hwChannel.getName().equals(name)) { + return hwChannel; + } + } + return null; + } + + + /** + * Return a read path which has a specific name. Return null if + * not found. + * + * @param name the name of the read path to search for. + * @return the read path with the specific name. + */ + public ReadPath getReadPath(String name) { + for (ReadPath p : _readPathList) { + if (p.getName().equals(name)) { + return p; + } + } + return null; + } + + /** + * Return a write path which has a specific name. Return null if + * not found. + * + * @param name the name of the write path to search for. + * @return the write path with the specific name. + */ + public WritePath getWritePath(String name) { + for (WritePath p : _writePathList) { + if (p.getName().equals(name)) { + return p; + } + } + return null; + } + + public Vector getVarList() { return _varList; } + + public Vector getConnectionList() { + return _connectionList; + } + + /** + * Compute all paths between processors. + */ + public void computePaths() { + for (WritePath w : _writePathList) { + String channelBuffer = w.getCHBuf().getName(); + for (ReadPath r : _readPathList) { + if (r.getCHBuf().getName().equals(channelBuffer)) { + Path path = new Path(); + path.setWritePath(w); + path.setReadPath(r); + _pathList.add(path); + } + } + } + + /* + //print all paths in this architecture + for (int j = 0; j < _pathList.size(); j++) { + Vector path = _pathList.elementAt(j).getPath(); + for (int k = 0; k < path.size(); k++) { + System.out.print(path.elementAt(k). + getName() + (k < path.size() - 1 ? " -> " : "")); + } + System.out.println(); + } + System.out.println("Found " + _pathList.size() + " paths."); + */ + + computeProcessorPath(); + } + + /** + * Compute all write path from one processor to another processor + * in this architecture. + */ + protected void computeProcessorPath() { + //iterate over all path in this architecture + for (int j = 0; j < _pathList.size(); j++) { + Processor startProcessor = _pathList.elementAt(j).getStartProcessor(); + Processor targetProcessor = _pathList.elementAt(j).getTargetProcessor(); + + //create new hashtable entry for this start processor + if (!_processorPathList.containsKey(startProcessor)) { + Hashtable> + processorPathList = new Hashtable>(); + _processorPathList.put(startProcessor, processorPathList); + } + + Hashtable> + processorPathList = _processorPathList. + get(startProcessor); + + //create new path list for this target processor + if(!processorPathList.containsKey(targetProcessor)) { + Vector processorPath = new Vector(); + processorPathList.put(targetProcessor, processorPath); + } + + Vector processorPath = + processorPathList.get(targetProcessor); + + //add this path to the list of paths + processorPath.add(_pathList.elementAt(j)); + } + + /* + //print all processor to processor path + int pathCounter = 0; + for (int j = 0; j < _processorList.size(); j++) { + for (int k = 0; k < _processorList.size(); k++) { + Vector processorPathList = + getPaths(_processorList.elementAt(j), + _processorList.elementAt(k)); + for (int l = 0; l < processorPathList.size(); l++) { + pathCounter++; + Vector path = processorPathList. + elementAt(l).getPath(); + for (int m = 0; m < path.size(); m++) { + System.out.print(path.elementAt(m).getName() + + (m < path.size() - 1 ? " -> " : "")); + } + System.out.println(); + } + } + } + System.out.println("Found " + pathCounter + " paths."); + */ + } + + /** + * Get all write paths from the given start processor to the given + * target processor. + * + * @param startProcessor processor where write path begins + * @param targetProcessor processor where write path ends + * @return vector of all write paths between startProcessor and + * targetProcessor + */ + public Vector getPaths( + Processor startProcessor, Processor targetProcessor) { + if (_processorPathList.get(startProcessor) == null) { + return null; + } + return _processorPathList.get(startProcessor).get(targetProcessor); + } + + /** + * Register the RX/TX/CH buffer to each resource. + * Can be used for dotty generation. + */ + public void registerRWPath2Resource() + { + for (ReadPath r : _readPathList) { + // register rxbuf + String memName = r.getRXBuf().getName(); + for (Memory m : _memoryList) { + if (m.getName().equals(memName)) { + m.getRXBufList().add(r.getName()); + } + } + + // register chbuf + memName = r.getCHBuf().getName(); + for (Memory m : _memoryList) { + if (m.getName().equals(memName)) { + m.getCHBufList().add(r.getName() + "_RPath"); + } + } + + // register pathes via a communication resource + for (HWChannel c : r.getHWChannelList()) { + if (c.getName().equals(r.getName())) { + // premise: a path cannot go via a bus twice. + c.getPathList().add(r.getName() + "_RPath"); + } + } + } + + for (WritePath w : _writePathList) { + // register txbuf + String memName = w.getTXBuf().getName(); + for (Memory m : _memoryList) { + if (m.getName().equals(memName)) { + m.getTXBufList().add(w.getName()); + } + } + + // register chbuf + memName = w.getCHBuf().getName(); + for (Memory m : _memoryList) { + if (m.getName().equals(memName)) { + m.getCHBufList().add(w.getName() + "_WPath"); + } + } + + // register pathes via a communication resource + for (HWChannel c : w.getHWChannelList()) { + if (c.getName().equals(w.getName())) { + // premise: a path cannot go via a bus twice. + c.getPathList().add(w.getName() + "_WPath"); + } + } + } + } + + /** List of the processors in the Architecture */ + protected Vector _processorList = null; + + /** List of the memories in the Architecture */ + protected Vector _memoryList = null; + + /** List of hwChannels in the Architecture */ + protected Vector _hwChannelList = null; + + /** List of variables in the Architecture */ + protected Vector _varList = null; + + /** List of connections in the Architecture: for simplified arch*/ + protected Vector _connectionList = null; + + /** List of read paths in the Architecture: for detail arch */ + protected Vector _readPathList = null; + + /** List of write paths in the Architecture: for detail arch */ + protected Vector _writePathList = null; + + /** List of paths in the Architecture */ + protected Vector _pathList = null; + + /** hashtable to access all processor to processor path */ + protected Hashtable>> _processorPathList = null; +} diff --git a/dol/src/dol/datamodel/architecture/Configuration.java b/dol/src/dol/datamodel/architecture/Configuration.java new file mode 100644 index 0000000..b370b95 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Configuration.java @@ -0,0 +1,113 @@ +/* $Id: Configuration.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + + +/** + * This class represents a name-value pair of a configuration tag in XML. + */ +public class Configuration { + + /** + * Constructor to create a Configuration. + */ + public Configuration(String name) { + _name = name; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + + public void accept(PNVisitor x) { + x.visitComponent(this); + } + */ + + + /** + * Clone this Configuration. + * + * @return new instance of the Configuration. + */ + public Object clone() { + try { + Configuration newObj = (Configuration) super.clone(); + newObj.setName(_name); + newObj.setValue(_value); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Configuration. + * + * @return the value of the configuration. + */ + public String getValue() { + return _value; + } + + /** + * Set the value of the Configuration. + * + * @param value the value of the configuration. + */ + public void setValue(String value) { + _value = value; + } + + /** + * Get the name of this configuration. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this configuration. + * + * @param name name of the configuration + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the hierarchical parent of this resource. + * + * @return parent of this resource + + public Resource getParentResource() { + return _parentResource; + } + */ + + /** + * Set the hierarchical parent of this resource. + * + * @param parentResource new parent + + public void setParentResource(Resource parentResource) { + _parentResource = parentResource; + } + */ + + /** + * Return a string representation of the Configuration. + * + * @return string representation of the Configuration + */ + public String toString() { + return "Configuration: " + getName(); + } + + protected String _value = null; + protected String _name = null; + protected ArchiResource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/architecture/HWChannel.java b/dol/src/dol/datamodel/architecture/HWChannel.java new file mode 100644 index 0000000..f4ed94e --- /dev/null +++ b/dol/src/dol/datamodel/architecture/HWChannel.java @@ -0,0 +1,128 @@ +/* $Id: HWChannel.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Iterator; +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class represents an interconnect element in the architecture. + */ +public class HWChannel extends ArchiResource { + /** + * Constructor to create a HWChannel with a name and an empty + * nodeList. + */ + public HWChannel(String name) { + super(name); + _pathList = new Vector(); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this HWChannel + * + * @return a new instance of the HWChannel. + */ + @SuppressWarnings("unchecked") + public Object clone() { + HWChannel newObj = (HWChannel) super.clone(); + newObj.setPathList((Vector)_pathList.clone()); + return (newObj); + } + + /** + * Get the range of this processor. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this process. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the type of this hw_channel. + * + * @return type + */ + public String getType() { + return _type; + } + + /** + * Set the type of this hw_channel. + * + * @param type new range value + */ + public void setType(String type) { + _type= type; + } + + /** + * Has this processor nodes? + * + * @return boolean value + */ + public boolean hasNodes() { + Iterator i = getNodeList().iterator(); + while (i.hasNext()) { + i.next(); + return true; + } + return false; + } + + /** + * Get the list of pathes via this resource. + * + * @return list of path. + */ + public Vector getPathList() { + return _pathList; + } + + /** + * Set the list of pathes via this resource. + * + * @param list path list + */ + public void setPathList(Vector list) { + _pathList = list; + } + + /** + * Return a description of the processor. + * + * @return a description of the processor. + */ + public String toString() { + return "HWChannel: " + getName() ; + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of processors. + */ + protected String _range; + protected String _type; + + // Store the name of pathes which go via this communication resource. + protected Vector _pathList; +} diff --git a/dol/src/dol/datamodel/architecture/Memory.java b/dol/src/dol/datamodel/architecture/Memory.java new file mode 100644 index 0000000..f7efe33 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Memory.java @@ -0,0 +1,170 @@ +/* $Id: Memory.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Iterator; +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class represents a memory element in the archietcture. + */ +public class Memory extends ArchiResource { + /** + * Constructor to create a Memory with a name and an empty + * nodeList. + */ + public Memory(String name) { + super(name); + _rxBufList = new Vector(); + _txBufList = new Vector(); + _chBufList = new Vector(); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Memory + * + * @return a new instance of the Memory. + */ + @SuppressWarnings("unchecked") + public Object clone() { + Memory newObj = (Memory) super.clone(); + newObj.setRXBufList((Vector)_rxBufList.clone()); + newObj.setTXBufList((Vector)_txBufList.clone()); + newObj.setCHBufList((Vector)_chBufList.clone()); + return (newObj); + } + + /** + * Get the range of this Memory. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this Memory. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the type of this Memory. + * + * @return type + */ + public String getType() { + return _type; + } + + /** + * Set the type of this Memory. + * + * @param type new range value + */ + public void setType(String type) { + _type= type; + } + + /** + * Get the list of RXBuf of this memory + * + * @return list of RX buffer. + */ + public Vector getRXBufList() { + return _rxBufList; + } + + /** + * Set the list of RXBuf of this memory. + * + * @param list RX buffer list + */ + public void setRXBufList(Vector list) { + _rxBufList = list; + } + + /** + * Get the list of TXBuf of this memory. + * + * @return list of TX buffers. + */ + public Vector getTXBufList() { + return _txBufList; + } + + /** + * Set the list of TXBuf of this memory. + * + * @param list TX buffer list + */ + public void setTXBufList(Vector list) { + _txBufList = list; + } + + /** + * Get the list of channel buffers of this memory. + * + * @return list of channel buffers. + */ + public Vector getCHBufList() { + return _chBufList; + } + + /** + * Set the list of channel Buf of this memory. + * + * @param list channel buffer list + */ + public void setCHBufList(Vector list) { + _chBufList = list; + } + + /** + * Has this memory nodes? + * + * @return boolean value + */ + public boolean hasNodes() { + Iterator i = getNodeList().iterator(); + while (i.hasNext()) { + i.next(); + return true; + } + return false; + } + + /** + * Return a description of the Memory. + * + * @return a description of the Memory. + */ + public String toString() { + return "Memory: " + getName() ; + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of Memories. + */ + protected String _range; + protected String _type; + + // store the name of rx, tx channel buffer, can be used for dotty gen. + protected Vector _rxBufList; + protected Vector _txBufList; + protected Vector _chBufList; +} diff --git a/dol/src/dol/datamodel/architecture/Node.java b/dol/src/dol/datamodel/architecture/Node.java new file mode 100644 index 0000000..89d51b8 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Node.java @@ -0,0 +1,211 @@ +/* $Id: Node.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class represents a node in the archietcture. + */ +public class Node implements Cloneable { + + /** + * Constructor to create a Node with a name and an empty + * portList. + */ + public Node(String name) { + _name = name; + _basename = name; + _portList = new Vector(); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Node + * + * @return a new instance of the Node. + */ + public Object clone() { + try { + Node newObj = (Node) super.clone(); + newObj.setName(_name); + newObj.setBasename(_basename); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the range of this node. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this node. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + + /** + * Get the name of this node. + * + * @return name of the node + */ + public String getName() { + return _name; + } + + /** + * Set the name of this node. + * + * @param name name of the node + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the basename of this node. + * + * @return basename of the node + */ + public String getBasename() { + return _basename; + } + + /** + * Set the basename of this node. + * + * @param basename name of the node + */ + public void setBasename(String basename) { + _basename = basename; + } + + /** + * Has this node IN/OUT/INOUT ports? + * + * @return boolean value + */ + public boolean hasInPorts() { + for (PortNode port : getPortList()) { + if (port.isInPort()) + return true; + } + return false; + } + + public boolean hasOutPorts() { + for (PortNode port : getPortList()) { + if (port.isOutPort()) + return true; + } + return false; + } + + public boolean hasInOutPorts() { + for (PortNode port : getPortList()) { + if (port.isInOutPort()) + return true; + } + return false; + } + + /** + * Return a port which has a specific name. Return null when port + * cannot be found. + * + * @param name name of the port to search for + * @return port with the specified name + */ + public PortNode getPort(String name) { + for (PortNode port : getPortList()) { + if (port.getName().equals(name)) { + return port; + } + } + return null; + } + + /** + * Return the first port on the List. + * + * @return first port element + */ + public PortNode getFirstPort() { + return (PortNode) _portList.firstElement(); + } + + /** + * Get the corresponding ArchiResource. + * + * @return the corresponding ArchiResource + */ + public ArchiResource getCorrespResource() { + return _correspResource; + } + + /** + * Set the corresponding ArchiResource. + * + * @param correspResource new ArchiResource + */ + public void setCorrespResource(ArchiResource correspResource) { + _correspResource = correspResource; + } + + /** + * Get the port list of a Node. + * + * @return the port list + */ + public Vector getPortList() { + return _portList; + } + + /** + * Set the port list of a Node. + * + * @param portList The new list + */ + public void setPortList( Vector portList ) { + _portList = portList; + } + + /** + * Return a description of the node. + * + * @return a description of the node. + */ + public String toString() { + return "Node: " + getName(); + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of nodes. + */ + protected String _range; + protected String _name = null; + protected String _basename = null; + protected ArchiResource _correspResource = null; + protected Vector _portList = null; +} diff --git a/dol/src/dol/datamodel/architecture/Path.java b/dol/src/dol/datamodel/architecture/Path.java new file mode 100644 index 0000000..c18084a --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Path.java @@ -0,0 +1,118 @@ +/* $Id: Path.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Iterator; +import java.util.Vector; + +/** + * Path consisting of write path and read path. + */ +public class Path { + /** + * Default constructor. + */ + public Path() { + _path = new Vector(); + } + + /** + * Set the read path of this path. + */ + public void setReadPath(ReadPath readPath) { + _readPath = readPath; + computeArchitectureResources(); + } + + /** + * Return the read path of this path. + * + * @return read path + */ + public ReadPath getReadPath() { + return _readPath; + } + + /** + * Set the write path of this path. + */ + public void setWritePath(WritePath writePath) { + _writePath = writePath; + computeArchitectureResources(); + } + + /** + * Return the write path of this path. + * + * @return write path + */ + public WritePath getWritePath() { + return _writePath; + } + + /** + * Return all resources contained in this path. + * + * @return all resources contained in this path + */ + public Vector getPath() { + return _path; + } + + /** + * Return the processor where this path starts. + * + * @return processor where this path starts + */ + public Processor getStartProcessor() { + if (_path.size() == 0) + return null; + + return (Processor)_path.elementAt(0); + } + + /** + * Return the processor where this path ends. + * + * @return processor where this path ends + */ + public Processor getTargetProcessor() { + if (_path.size() == 0) + return null; + + return (Processor)_path.elementAt(_path.size() - 1); + } + + /** + * Compute the vector of all resources contained in this path. + */ + protected void computeArchitectureResources() { + _path.clear(); + + if (_readPath == null || _writePath == null) + return; + + _path.add(_writePath.getProcessor()); + _path.add(_writePath.getTXBuf()); + Iterator channelIterator = + _writePath.getHWChannelList().iterator(); + while (channelIterator.hasNext()) { + _path.add(channelIterator.next()); + } + _path.add(_readPath.getCHBuf()); + channelIterator = _readPath.getHWChannelList().iterator(); + while (channelIterator.hasNext()) { + _path.add(channelIterator.next()); + } + _path.add(_readPath.getRXBuf()); + _path.add(_readPath.getProcessor()); + } + + /** read path of this path*/ + protected ReadPath _readPath = null; + + /** write path of this path */ + protected WritePath _writePath = null; + + /** resources contained in this path */ + Vector _path; +} diff --git a/dol/src/dol/datamodel/architecture/PortNode.java b/dol/src/dol/datamodel/architecture/PortNode.java new file mode 100644 index 0000000..2db5cd1 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/PortNode.java @@ -0,0 +1,253 @@ +/* $Id: PortNode.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import dol.visitor.ArchiVisitor; + +/** + * This class represents a port of an architectural node. + */ +public class PortNode implements Cloneable { + /** + * Constructor to create a PortNode with a name. + */ + public PortNode(String name) { + _name = name; + _isInPort = false; + _isOutPort = false; + _isInOutPort = false; + } + + /** + * Constructor to create a PortNode with a name and a type. + */ + public PortNode(String name, boolean type) { + _name = name; + _isInPort = (type == INPORT); + _isOutPort = (type == OUTPORT); + _isInOutPort = (type == INOUTPORT); + } + + /** + * Accept a visitor. + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this PortNode + * + * @return new instance of the PortNode + */ + public Object clone() { + try { + PortNode newObj = (PortNode) super.clone(); + newObj.setName(_name); + newObj.setBasename(_basename); + newObj.setPeerPort(_peerPort); + newObj.setPeerResource(_peerResource); + newObj.setPeerNode(_peerNode); + newObj.setResource(_resource ); + newObj.setNode(_node); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Check whether this port is an inport. + * + * @return true if this port is an inport, otherwise false + */ + public boolean isInPort() { + return _isInPort; + } + + /** + * Check whether this port is an outport. + * + * @return true if this port is an outport, otherwise false + */ + public boolean isOutPort() { + return _isOutPort; + } + + /** + * Check whether this port is an inoutport. + * + * @return true if this port is an inoutport, otherwise false + */ + public boolean isInOutPort() { + return _isInOutPort; + } + + /** + * Get the name of this port. + * + * @return name of the port + */ + public String getName() { + return _name; + } + + /** + * Set the name of this port. + * + * @param name name of the port + */ + public void setName(String name) { + _name = name; + } + + public void setBasename(String basename) { _basename = basename; } + public String getBasename() { return _basename; } + + /** + * Get the range of this port. + * + * @return range of the port + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this port. + * + * @param range range of the port + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the resource/node of this port. + * + * @return the resource/node + */ + public ArchiResource getResource() { + return _resource; + } + public Node getNode() { + return _node; + } + + /** + * Set the resource/node of this port. + * + * @param resource + */ + public void setResource(ArchiResource resource) { + _resource = resource; + } + public void setNode(Node node) { + _node = node; + } + + /** + * Set the peer resource/node/port of this port. + * + * @param peer The new node + */ + public void setPeerPort(PortNode peer) { _peerPort = peer; } + + /** + * + */ + public void setPeerResource(ArchiResource n) { _peerResource = n; } + + /** + * + */ + public void setPeerNode(Node n) { _peerNode = n; } + + /** + * Get the peer resource/node/port of this port. + * + * @return the peer resource/node/port + */ + public PortNode getPeerPort(){ return _peerPort; } + + /** + * + */ + public ArchiResource getPeerResource() { return _peerResource; } + + /** + * + */ + public Node getPeerNode() { return _peerNode; } + + /** + * Get the type of this port. + * + * @return type of this port + */ + public String getType() { + if (_isInPort) return "input"; + if (_isOutPort) return "output"; + if (_isInOutPort) return "duplex"; + return "type not set."; + } + + /** + * Return a string representation of the port. + * + * @return string representation of the port + */ + public String toString() { + return "PortNode: " + _name; + } + + /** constant for inport for usage in the constructor **/ + public static final boolean INPORT = true; + + /** constant for outport for usage in the constructor **/ + public static final boolean OUTPORT = false; + + /** constant for inoutport for usage in the constructor **/ + public static final boolean INOUTPORT = false; + + /** defines whether this port is an inport **/ + protected boolean _isInPort = false; + + /** defines whether this port is an outport **/ + protected boolean _isOutPort = false; + + /** defines whether this port is an inout **/ + protected boolean _isInOutPort = false; + + /** name of the port */ + protected String _name = null; + + /** basename of the resource, if no basename, store the name */ + protected String _basename = null; + + /** resource (process or channel) this port belongs to */ + protected ArchiResource _resource = null; + + /** node this port belongs to */ + protected Node _node = null; + + /** resource which peerPort belongs to */ + protected ArchiResource _peerResource = null; + + /** node which peerPort belongs to */ + protected Node _peerNode = null; + + /** connected peer port name */ + protected PortNode _peerPort = null; + + /** peer channel name */ + protected String _channelName = null; + + /** + * Range of the iterator when the instance belongs to an iterated + * series of ports. + */ + protected String _range = null; +} diff --git a/dol/src/dol/datamodel/architecture/Processor.java b/dol/src/dol/datamodel/architecture/Processor.java new file mode 100644 index 0000000..cffc781 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Processor.java @@ -0,0 +1,135 @@ +/* $Id: Processor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.visitor.ArchiVisitor; + +/** + * This class represents a processor element in the architecture. + */ +public class Processor extends ArchiResource { + + /** + * Constructor to create a Processor with a name and an empty + * nodeList. + */ + public Processor(String name) { + super(name); + _processList = new Vector(); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Processor + * + * @return a new instance of the Processor. + */ + @SuppressWarnings("unchecked") + public Object clone() { + Processor newObj = (Processor) super.clone(); + newObj.setProcessList((Vector)_processList.clone()); + return (newObj); + } + + /** + * Get the range of this processor. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this process. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the type of this processor. + * + * @return type + */ + public String getType() { + return _type; + } + + /** + * Set the type of this processor. + * + * @param type new range value + */ + public void setType(String type) { + _type= type; + } + + + + /** + * Set the list of processes bound to this processor. + * @param processList the new process list + */ + public void setProcessList(Vector processList) + { + _processList = processList; + } + + /** + * Get the list of processes bound to this processor. + */ + public Vector getProcessList() + { + return _processList; + } + + /** + * Indicates if a process with a specific name is bound + * to this processor. + * @param name the name of the process to search for + * @return true if a process with the specifed name is bound to this + * processor + */ + public boolean hasProcess(String name) + { + for (Process iter : _processList) { + if (iter.getName().equals(name)) + return true; + } + + return false; + } + + /** + * Return a description of the processor. + * + * @return a description of the processor. + */ + public String toString() { + return "Processor: " + getName() ; + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of processors. + */ + protected String _range; + protected String _type; + + /** + * List of processes bound to this processor. + */ + protected Vector _processList; +} diff --git a/dol/src/dol/datamodel/architecture/ReadPath.java b/dol/src/dol/datamodel/architecture/ReadPath.java new file mode 100644 index 0000000..ce380c1 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/ReadPath.java @@ -0,0 +1,115 @@ +/* $Id: ReadPath.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class defines a read path. A read path contains one processor, + * one rxbuf, one channel buf, and one or more hw channels. + */ +public class ReadPath extends ArchiResource { + /** + * Constructor to create an architecture connection with a name. + * + */ + public ReadPath(String name) { + super(name); + _hwChannelList = new Vector(); + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this architectural connection. + * + * @return a new instance of the architectural connection. + */ + /* + @SuppressWarnings("unchecked") + public Object clone() { + ReadPath newObj = (ReadPath) super.clone(); + newObj.setProcessor(_processor); + newObj.setRXBuf(_rxBuf); + newObj.setCHBuf(_chBuf); + newObj.setHWChannelNameList((Vector)_hwChannelNameList.clone()); + return (newObj); + } + */ + + /** + * Return a string representation of the architectural connection. + * + * @return string representation of the architectural connection + */ + public String toString() { + return "ReadPath: " + getName(); + } + + /** + * Return the memory where the receive buffer is located. + * + * @return name of memory + */ + public Memory getRXBuf() { return _rxBuf; } + + /** + * Set the receive buffer location. + * + * @param rxbuf rxbuf location + */ + public void setRXBuf(Memory rxbuf) { _rxBuf = rxbuf; } + + /** + * Return the memory where the channel buffer is located. + * + * @return channel buffer location + */ + public Memory getCHBuf() { return _chBuf; } + + /** + * Set the channel buffer location + * + * @param chbuf memory where channel buffer is located. + */ + public void setCHBuf(Memory chbuf) { _chBuf = chbuf; } + + /** + * Return the list of the HW channels. + * + * @return list of HW channels + */ + public Vector getHWChannelList() { return _hwChannelList; } + + /** + * Return the processor to which this path is associated. + * + * @return processor + */ + public Processor getProcessor() { + return _processor; + } + + /** + * Set the processor to which this path is associated + * + * @param processor processor + */ + public void setProcessor(Processor processor) { + _processor = processor; + } + + + protected Processor _processor; + protected Memory _rxBuf; + protected Memory _chBuf; + protected Vector _hwChannelList; +} diff --git a/dol/src/dol/datamodel/architecture/Variable.java b/dol/src/dol/datamodel/architecture/Variable.java new file mode 100644 index 0000000..2c91a2d --- /dev/null +++ b/dol/src/dol/datamodel/architecture/Variable.java @@ -0,0 +1,109 @@ +/* $Id: Variable.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import dol.visitor.ArchiVisitor; + +/** + * This class represents a global variable in the architecture XML. + */ +public class Variable implements Cloneable { + + /** + * Constructor to create a Variable. + */ + public Variable(String name) { + _name = name; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Variable. + * + * @return new instance of the Variable. + */ + public Object clone() { + try { + Variable newObj = (Variable) super.clone(); + newObj.setName(_name); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Variable. + * + * @return the value of the variable + */ + public int getValue() { + return _value; + } + + /** + * Set the value of the Variable. + * + * @param value the value of the variable + */ + public void setValue(int value) { + _value = value; + } + + /** + * Get the name of this SourceCode. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this SourceCode. + * + * @param name name of the SourceCode + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the parent ArchiResource. + * + * @return parent ArchiResource + */ + public ArchiResource getParentResource() { + return _parentResource; + } + + /** + * Set the parent ArchiResource. + * + * @param parentResource new parent + */ + public void setParentResource(ArchiResource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the Variable. + * + * @return string representation of the Variable + */ + public String toString() { + return "Variable: " + getName(); + } + + protected int _value; + protected String _name = null; + protected ArchiResource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/architecture/WritePath.java b/dol/src/dol/datamodel/architecture/WritePath.java new file mode 100644 index 0000000..80244a7 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/WritePath.java @@ -0,0 +1,113 @@ +/* $Id: WritePath.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.architecture; + +import java.util.Vector; + +import dol.visitor.ArchiVisitor; + +/** + * This class defines a write path. A write path contains one processor, + * one txbuf, one channel buf, and one or more hw channels. + */ +public class WritePath extends ArchiResource { + /** + * Constructor to create an architecture connection with a name. + */ + public WritePath(String name) { + super(name); + _hwChannelList = new Vector(); + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(ArchiVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this architectural connection. + * + * @return a new instance of the architectural connection. + */ + /* + @SuppressWarnings("unchecked") + public Object clone() { + WritePath newObj = (WritePath) super.clone(); + newObj.setProcessor(_processor); + newObj.setTXBuf(_txBuf); + newObj.setCHBuf(_chBuf); + newObj.setHWChannelNameList((Vector)_hwChannelNameList.clone()); + return (newObj); + } + */ + + /** + * Return a string representation of the architectural connection. + * + * @return string representation of the architectural connection + */ + public String toString() { + return "WritePath: " + getName(); + } + + /** + * Return the memory where the transmit buffer is located. + * + * @return name of memory + */ + public Memory getTXBuf() { return _txBuf; } + + /** + * Set the transmit buffer location. + * + * @param txbuf txbuf location + */ + public void setTXBuf(Memory txbuf) { _txBuf = txbuf; } + + /** + * Return the memory where the channel buffer is located. + * + * @return channel buffer location + */ + public Memory getCHBuf() { return _chBuf; } + + /** + * Set the channel buffer location + * + * @param chbuf memory where channel buffer is located. + */ + public void setCHBuf(Memory chbuf) { _chBuf = chbuf; } + + /** + * Return the list of the HW channels. + * + * @return list of HW channels + */ + public Vector getHWChannelList() { return _hwChannelList; } + + /** + * Return the processor to which this path is associated. + * + * @return processor + */ + public Processor getProcessor() { + return _processor; + } + + /** + * Set the processor to which this path is associated. + * + * @param processor processor + */ + public void setProcessor(Processor processor) { + _processor = processor; + } + + protected Processor _processor; + protected Memory _txBuf; + protected Memory _chBuf; + protected Vector _hwChannelList; +} diff --git a/dol/src/dol/datamodel/architecture/package.html b/dol/src/dol/datamodel/architecture/package.html new file mode 100644 index 0000000..a6f1658 --- /dev/null +++ b/dol/src/dol/datamodel/architecture/package.html @@ -0,0 +1,20 @@ + + + + + + +Internal data model for the architecture representation. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/datamodel/mapping/Binding.java b/dol/src/dol/datamodel/mapping/Binding.java new file mode 100644 index 0000000..ac39c52 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/Binding.java @@ -0,0 +1,87 @@ +package dol.datamodel.mapping; + +import dol.visitor.MapVisitor; + +/** + * This class represents a binding element in the mapping. + */ +abstract public class Binding extends MapResource { + + /** + * Constructor to create a Binding with a name. + */ + public Binding(String name) { + super(name); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Binding + * + * @return a new instance of the Binding. + */ + public Object clone() { + Binding newObj = (Binding) super.clone(); + return (newObj); + } + + /** + * Get the range of this binding. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this binding. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the type of this binding. + * + * @return type + */ + public String getType() { + return _type; + } + + /** + * Return a description of the binding. + * + * @return a description of the binding. + */ + public String toString() { + return "Binding: " + getName() ; + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of bindings. + */ + protected String _range; + protected String _type; + + /** + * Type specifier for communication bindings. + */ + public static final String COMMUNICATION = "communication"; + + /** + * Type specifier for computation bindings. + */ + public static final String COMPUTATION = "computation"; +} diff --git a/dol/src/dol/datamodel/mapping/CommunicationBinding.java b/dol/src/dol/datamodel/mapping/CommunicationBinding.java new file mode 100644 index 0000000..568c326 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/CommunicationBinding.java @@ -0,0 +1,89 @@ +/* $Id: CommunicationBinding.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.WritePath; +import dol.datamodel.pn.Channel; +import dol.visitor.MapVisitor; + +/** + * This class represents a communication binding element in the mapping. + */ +public class CommunicationBinding extends Binding { + + /** + * Constructor to create a CommunicationBinding with a name. + */ + public CommunicationBinding(String name) { + super(name); + _type = COMMUNICATION; + } + + /** + * Clone this Binding + * + * @return a new instance of the Binding. + */ + public Object clone() { + CommunicationBinding newObj = (CommunicationBinding) super.clone(); + newObj.setChannel(_channel); + newObj.setReadPath(_readPath); + newObj.setWritePath(_writePath); + return (newObj); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** Set the SW channel */ + public void setChannel(Channel c) { + _channel = c; + } + + /** Get the SW channel */ + public Channel getChannel() { + return _channel; + } + + /** Set the read path */ + public void setReadPath(ReadPath p) { + _readPath = p; + } + + /** Get the read path */ + public ReadPath getReadPath() { + return _readPath; + } + + /** Set the write path */ + public void setWritePath(WritePath p) { + _writePath = p; + } + + /** Get the write path */ + public WritePath getWritePath() { + return _writePath; + } + + /** + * Return a description of the binding. + * + * @return a description of the binding. + */ + public String toString() { + return "CommunicationBinding: " + getName() ; + } + + private Channel _channel = null; + + private ReadPath _readPath = null; + + private WritePath _writePath = null; + +} diff --git a/dol/src/dol/datamodel/mapping/ComputationBinding.java b/dol/src/dol/datamodel/mapping/ComputationBinding.java new file mode 100644 index 0000000..c117265 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/ComputationBinding.java @@ -0,0 +1,75 @@ +/* $Id: ComputationBinding.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.pn.Process; +import dol.visitor.MapVisitor; + +/** + * This class represents a computation binding element in the mapping. + */ +public class ComputationBinding extends Binding { + + /** + * Constructor to create a ComputationBinding with a name. + */ + public ComputationBinding(String name) { + super(name); + _type = COMPUTATION; + } + + /** + * Clone this Binding + * + * @return a new instance of the Binding. + */ + public Object clone() { + ComputationBinding newObj = (ComputationBinding) super.clone(); + newObj.setProcessor(_processor); + newObj.setProcess(_process); + return (newObj); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** Set the PN process */ + public void setProcess(Process p) { + _process = p; + } + + /** Get the PN process */ + public Process getProcess() { + return _process; + } + + /** Set the processor from the architecture */ + public void setProcessor(Processor p) { + _processor = p; + } + + /** Get the processor */ + public Processor getProcessor() { + return _processor; + } + + /** + * Return a description of the binding. + * + * @return a description of the binding. + */ + public String toString() { + return "ComputationBinding: " + getName() ; + } + + private Process _process = null; + + private Processor _processor = null; + +} diff --git a/dol/src/dol/datamodel/mapping/Configuration.java b/dol/src/dol/datamodel/mapping/Configuration.java new file mode 100644 index 0000000..fe3a3a4 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/Configuration.java @@ -0,0 +1,75 @@ +/* $Id: Configuration.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +import dol.visitor.MapVisitor; + +/** + * This class represents a name-value pair of a configuration tag in XML. + */ +public class Configuration { + + /** + * Constructor to create a Configuration. + */ + public Configuration(String name, String value) { + _name = name; + _value = value; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Configuration. + * + * @return new instance of the Configuration. + */ + public Object clone() { + try { + Configuration newObj = (Configuration) super.clone(); + newObj._name = _name; + newObj._value = _value; + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Configuration. + * + * @return the value of the configuration. + */ + public String getValue() { + return _value; + } + + /** + * Get the name of this configuration. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Return a string representation of the Configuration. + * + * @return string representation of the Configuration + */ + public String toString() { + return "Configuration: " + _name + "/" + _value; + } + + protected String _value = null; + protected String _name = null; + +} \ No newline at end of file diff --git a/dol/src/dol/datamodel/mapping/MapResource.java b/dol/src/dol/datamodel/mapping/MapResource.java new file mode 100644 index 0000000..2aa9cf8 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/MapResource.java @@ -0,0 +1,116 @@ +package dol.datamodel.mapping; + +import dol.visitor.MapVisitor; + +/** + * This class is the basic class which abstracts a mapping resource. + */ +public class MapResource implements Cloneable { + /** + * Constructor to create a mapping resource with a name. + */ + public MapResource(String name) { + _name = name; + _basename = name; + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this mapping resource. + * + * @return new instance of the mapping resource. + */ + public Object clone() { + try { + MapResource newObj = (MapResource) super.clone(); + newObj.setName(_name); + newObj.setBasename(_basename); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the name of this mapping resource. + * + * @return name of the mapping resource + */ + public String getName() { + return _name; + } + + /** + * Set the name of this mapping resource. + * + * @param name name of the mapping resource + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the basename of this mapping resource. + * + * @return basename of the mapping resource + */ + public String getBasename() { + return _basename; + } + + /** + * Set the basename of this mapping resource. + * + * @param basename name of the mapping resource + */ + public void setBasename(String basename) { + _basename = basename; + } + + /** + * Get the hierarchical parent of this mapping resource. + * + * @return parent of this mapping resource + */ + public MapResource getParentResource() { + return _parentResource; + } + + /** + * Set the hierarchical parent of this mapping resource. + * + * @param parentResource new parent + */ + public void setParentResource(MapResource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the architectural resource. + * + * @return string representation of the architectural resource + */ + public String toString() { + return "MapResource: " + _name; + } + + /** name of the architectural resource */ + protected String _name = null; + + /** basename of the architectural resource, if no basename, store the name */ + protected String _basename = null; + + /** + * parent resource of this architectural resource in a hierarchical architecture network + */ + protected MapResource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/mapping/Mapping.java b/dol/src/dol/datamodel/mapping/Mapping.java new file mode 100644 index 0000000..c0123a5 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/Mapping.java @@ -0,0 +1,274 @@ +package dol.datamodel.mapping; + +import java.util.Iterator; +import java.util.Vector; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Processor; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.MapVisitor; + + +/** + * This class defines a mapping. + */ +public class Mapping extends MapResource +{ + public Mapping(String name) + { + super(name); + _compBindList = new Vector(); + _commBindList = new Vector(); + _scheduleList = new Vector(); + _variableList = new Vector(); + /** + * For some code generation back end, traversing via architecture + * model rather than mapping model is more convenient, since the + * mapping model is a flattened view of the binding where one can + * not access all the binded processes from a processor. Therefore + * we annotate the binding information back to processor. + */ + _processList = new Vector(); + _processorList = new Vector(); + } + + /** + * Accept a visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + + /** + * Clone this Mapping. + * + * @return new instance of the Mapping + */ + @SuppressWarnings("unchecked") + public Object clone() { + Mapping newObj = (Mapping) super.clone(); + newObj.setCompBindList((Vector)_compBindList.clone()); + newObj.setCommBindList((Vector)_commBindList.clone()); + newObj.setScheduleList((Vector)_scheduleList.clone()); + newObj.setVarList((Vector)_variableList.clone()); + newObj.setProcessorList((Vector)_processorList.clone()); + newObj.setProcessList((Vector)_processList.clone()); + return (newObj); + } + + /** Get the process network. */ + public ProcessNetwork getPN() { return _pn; } + + /** Store a reference to the process network. */ + public void setPN(ProcessNetwork pn) { _pn = pn; } + + /** Get the architecture */ + public Architecture getArch() { return _arch; } + + /** Store a reference to the architecture. */ + public void setArch(Architecture arch) { _arch = arch; } + + /** + * Get a list of computation bindings in this Mapping. + * + * @return the binding list + */ + public Vector getCompBindList() { + return _compBindList; + } + + /** + * Get a list of processes in this Mapping. + * + * @return the process list + */ + public Vector getProcessList() { + return _processList; + } + + /** + * Return a process which has a specific name. Return null if + * process cannot be found. + * + * @param name the name of the process to search for. + * @return the process with the specific name or null if not found. + */ + public Process getProcess(String name) + { + for (Process process : _processList) { + if (process.getName().equals(name)) { + return process; + } + } + return null; + } + /** + * Set the computation binding list of this mapping. + * + * @param compBindList The new list + */ + public void setCompBindList( Vector compBindList ) { + _compBindList = compBindList; + } + + /** + * Get a list of communication bindings in this Mapping. + * + * @return the binding list + */ + public Vector getCommBindList() { + return _commBindList; + } + + /** + * Set the communication binding list of this mapping. + * + * @param commBindList The new list + */ + public void setCommBindList( Vector commBindList ) { + _commBindList = commBindList; + } + + /** + * Set the process list of this mapping. + * + * @param processList The new list + */ + public void setProcessList( Vector processList ) { + _processList = processList; + } + + /** + * Get the a list of processors in this Mapping. + * + * @return the processor list + */ + public Vector getProcessorList() { + return _processorList; + } + + /** + * Set the processor list of this mapping. + * + * @param processorList The new list + */ + public void setProcessorList( Vector processorList ) { + _processorList = processorList; + } + + /** + * Return a processor which has a specific name. Return null if + * processor cannot be found. + * + * @param name the name of the processor to search for. + * @return the processor with the specific name or null if not found. + */ + public Processor getProcessor(String name) + { + Iterator i; + i = _processorList.iterator(); + while (i.hasNext()) { + Processor processor = i.next(); + if (processor.getName().equals(name)) { + return processor; + } + } + return null; + } + + /** + * Get the a list of schedules in this Mapping. + * + * @return the schedule list + */ + public Vector getScheduleList() { + return _scheduleList; + } + + /** + * Set the schedule list of this mapping. + * + * @param scheduleList The new list + */ + public void setScheduleList( Vector scheduleList ) { + _scheduleList = scheduleList; + } + + /** + * Return the schedule for a resource that has a specific name. + * Return null if schedule cannot be found. + * + * @param name the name of the resource for which we are looking for the schedule. + * @return the schedule for the resource with the specified name. + */ + public Schedule getScheduleByResource(String name) { + for(Schedule schedule : _scheduleList) { + if (schedule.getResource().getName().equals(name)) { + return schedule; + } + } + return null; + } + + /** + * Return the communication binding for a channel that has a specific name. + * Return null if binding cannot be found. + * + * @param name the name of the channel for which we are looking for the binding. + * @return the binding for the channel with the specified name. + */ + public CommunicationBinding getCommBindingByChannel(String name) { + for(CommunicationBinding b : _commBindList) { + if (b.getChannel().getName().equals(name)) { + return b; + } + } + return null; + } + + /** + * Get the variable list of a Mapping. + * + * @return the variable list + */ + public Vector getVarList() { + return _variableList; + } + + /** + * Set the variable list of a mapping. + * + * @param variableList The new list + */ + public void setVarList( Vector variableList ) { + _variableList = variableList; + } + + /** ProcessNetwork Reference **/ + protected ProcessNetwork _pn = null; + + /** Architecture Reference **/ + protected Architecture _arch = null; + + /** List of processes in the mapping **/ + protected Vector _processList = null; + + /** List of processors actually used in the mapping **/ + protected Vector _processorList = null; + + /** List of computations bindings in the mapping **/ + protected Vector _compBindList = null; + + /** List of communication bindings in the mapping **/ + protected Vector _commBindList = null; + + /** List of schedules in the mapping **/ + protected Vector _scheduleList = null; + + /** List of variables in the mapping **/ + protected Vector _variableList = null; +} diff --git a/dol/src/dol/datamodel/mapping/Schedule.java b/dol/src/dol/datamodel/mapping/Schedule.java new file mode 100644 index 0000000..688afca --- /dev/null +++ b/dol/src/dol/datamodel/mapping/Schedule.java @@ -0,0 +1,160 @@ +/* $Id: Schedule.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +import java.util.Vector; + +import dol.datamodel.architecture.ArchiResource; +import dol.visitor.MapVisitor; + +/** + * This class represents a schedule element in the mapping. + */ +public class Schedule extends MapResource { + + /** + * Constructor to create a Schedule with a name. + */ + public Schedule(String name) { + super(name); + _entryList = new Vector(); + _cfgList = new Vector(); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Schedule resource. + * + * @return new instance of the Schedule resource. + */ + @SuppressWarnings("unchecked") + public Object clone() { + Schedule newObj = (Schedule) super.clone(); + newObj.setResource(_resource); + newObj.setSchedPolicy(_policy); + newObj.setCfgList((Vector)_cfgList.clone() ); + newObj.setEntryList((Vector)_entryList.clone()); + return (newObj); + } + + /** Set the resource, a HW channel or a processor from the architecture */ + public void setResource(ArchiResource r) { + _resource = r; + } + + /** Get the resource */ + public ArchiResource getResource() { + return _resource; + } + + /** Set the scheduling policy */ + public void setSchedPolicy(SchedulingPolicy p) { + _policy = p; + } + + /** Get the scheduling policy */ + public SchedulingPolicy getSchedPolicy() { + return _policy; + } + + /** + * Get the list of configurations of this schedule. + * + * @return list of configurations + */ + public Vector getCfgList() { + return _cfgList; + } + + /** + * Set the list of configurations of this schedule. + * + * @param cfgList configuration list + */ + public void setCfgList(Vector cfgList) { + _cfgList = cfgList; + } + + /** + * Set the list of scheduler table entries. + * + * @param entryList The new list + */ + public void setEntryList(Vector entryList) { + _entryList = entryList; + } + + /** Get the list of scheduler table entries */ + public Vector getEntryList() { + return _entryList; + } + + /** + * Return the scheduler table entry with its schedule configuration + * that has a specific name. + * Return null if the entry cannot be found. + * + * @param name the name of the entry for which we are looking for. + * @return the entry with its schedule configuration with the specified name. + */ + public ScheduleEntry getScheduleEntry(String name) { + for(ScheduleEntry entry : _entryList) { + if (entry.getName().equals(name)) { + return entry; + } + } + return null; + } + + /** + * Return the value for the given configuration key. Return null + * when the key cannot be found. + * + * @param name name of the configuration key to search for + * @return value of the specified configuration key + */ + public String getCfgValue(String name) { + for(Configuration c : _cfgList) { + if(c.getName().equals(name)) { + return c.getValue(); + } + } + return null; + } + + /** + * Return a string representation of the Schedule. + * + * @return string representation of the Schedule + */ + public String toString() { + return "Schedule: " + getName(); + } + + /** Resource that is scheduled */ + ArchiResource _resource = null; + + /** Scheduling policy */ + SchedulingPolicy _policy = null; + + /** List of the configurations of the Schedule */ + protected Vector _cfgList = null; + + /** List of scheduler table entries */ + Vector _entryList = null; + + /** Configuration key for the slots per cycle of a TDMA scheduler */ + final public static String cfdTdmaSlotsPerCycle = "slotsonecycle"; + + /** Configuration key for the general slot length + * in nanoseconds of a TDMA scheduler */ + final public static String cfgTdmaSlotLength = "slotlength"; + +} diff --git a/dol/src/dol/datamodel/mapping/ScheduleEntry.java b/dol/src/dol/datamodel/mapping/ScheduleEntry.java new file mode 100644 index 0000000..2ee5331 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/ScheduleEntry.java @@ -0,0 +1,113 @@ +/* $Id: ScheduleEntry.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +import java.util.Vector; + +import dol.datamodel.pn.Schedulable; +import dol.visitor.MapVisitor; + +/** + * This class represents an origin element in the mapping which refers + * to a PN process or a SW channel with a configuration element. + */ +public class ScheduleEntry extends MapResource { + + /** + * Constructor to create a ScheduleProcess with a name. + */ + public ScheduleEntry(String name) { + super(name); + _cfgList = new Vector(); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this ScheduleProcess resource. + * + * @return new instance of the ScheduleProcess resource. + */ + @SuppressWarnings("unchecked") + public Object clone() { + ScheduleEntry newObj = (ScheduleEntry) super.clone(); + newObj.setConsumer(_consumer); + newObj.setCfgList((Vector)_cfgList.clone()); + return (newObj); + } + + /** + * Get the list of configurations of this scheduled process. + * + * @return list of configurations + */ + public Vector getCfgList() { + return _cfgList; + } + + /** + * Set the list of configurations of this scheduled process. + * + * @param cfgList configuration list + */ + public void setCfgList(Vector cfgList) { + _cfgList = cfgList; + } + + /** Set the consumer */ + public void setConsumer(Schedulable c) { + _consumer = c; + } + + /** Get the consumer */ + public Schedulable getConsumer() { + return _consumer; + } + + /** + * Return the value for the given configuration key. Return null + * when the key cannot be found. + * + * @param name name of the configuration key to search for + * @return value of the specified configuration key + */ + public String getCfgValue(String name) { + for(Configuration c : _cfgList) { + if(c.getName().equals(name)) { + return c.getValue(); + } + } + return null; + } + + /** + * Return a string representation of the ScheduleProcess. + * + * @return string representation of the ScheduleProcess + */ + public String toString() { + return "ScheduleProcess: " + getName(); + } + + /** Consumer: PN process or SW channel */ + private Schedulable _consumer = null; + + /** list of the configurations of the ScheduleProcess */ + protected Vector _cfgList = null; + + /** Configuration key for the priority of a task in a fixed priority scheduler */ + final public static String cfgFpPriority = "priority"; + + /** Configuration key for the index of the TDMA slot of a process */ + final public static String cfgTdmaStartSlot = "startslot"; + + /** Configuration key for the number of TDMA slots for a process */ + final public static String cfgTdmaNumberOfSlots = "numberofslots"; + +} diff --git a/dol/src/dol/datamodel/mapping/SchedulingPolicy.java b/dol/src/dol/datamodel/mapping/SchedulingPolicy.java new file mode 100644 index 0000000..6589174 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/SchedulingPolicy.java @@ -0,0 +1,43 @@ +/* $Id: SchedulingPolicy.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.mapping; + +/** + * This class defines the supported scheduling policies. + */ +public enum SchedulingPolicy { + + STATIC, FIXEDPRIORITY, FIFO, TDMA, ROUNDROBIN; + + private static final String staticVal = "static"; + private static final String fixedPriorityVal = "fixedpriority"; + private static final String fifoVal = "fifo"; + private static final String tdmaVal = "tdma"; + private static final String roundRobinVal = "roundrobin"; + + static public SchedulingPolicy fromString(String s) { + if(s.equals(staticVal)) { + return SchedulingPolicy.STATIC; + } else if(s.equals(fixedPriorityVal)) { + return SchedulingPolicy.FIXEDPRIORITY; + } else if(s.equals(fifoVal)) { + return SchedulingPolicy.FIFO; + } else if(s.equals(tdmaVal)) { + return SchedulingPolicy.TDMA; + } + return SchedulingPolicy.ROUNDROBIN; + } + + static public String toString(SchedulingPolicy t) { + if(t == SchedulingPolicy.STATIC) { + return staticVal; + } else if(t == SchedulingPolicy.FIXEDPRIORITY) { + return fixedPriorityVal; + } else if(t == SchedulingPolicy.FIFO) { + return fifoVal; + } else if(t == SchedulingPolicy.TDMA) { + return tdmaVal; + } + return roundRobinVal; + } + +} \ No newline at end of file diff --git a/dol/src/dol/datamodel/mapping/Variable.java b/dol/src/dol/datamodel/mapping/Variable.java new file mode 100644 index 0000000..a097b87 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/Variable.java @@ -0,0 +1,108 @@ +package dol.datamodel.mapping; + +import dol.visitor.MapVisitor; + +/** + * This class represents a global variable in the mapping XML. + */ +public class Variable implements Cloneable { + + /** + * Constructor to create a Variable. + */ + public Variable(String name) { + _name = name; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(MapVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Variable. + * + * @return new instance of the Variable. + */ + public Object clone() { + try { + Variable newObj = (Variable) super.clone(); + newObj.setName(_name); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Variable. + * + * @return the value of the variable + */ + public int getValue() { + return _value; + } + + /** + * Set the value of the Variable. + * + * @param value the value of the variable + */ + public void setValue(int value) { + _value = value; + } + + /** + * Get the name of this SourceCode. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this SourceCode. + * + * @param name name of the SourceCode + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the parent ArchiResource. + * + * @return parent ArchiResource + */ + public MapResource getParentResource() { + return _parentResource; + } + + /** + * Set the parent ArchiResource. + * + * @param parentResource new parent + */ + public void setParentResource(MapResource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the Variable. + * + * @return string representation of the Variable + */ + public String toString() { + return "Variable: " + getName(); + } + + protected int _value; + protected String _name = null; + protected MapResource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/mapping/package.html b/dol/src/dol/datamodel/mapping/package.html new file mode 100644 index 0000000..38cfb44 --- /dev/null +++ b/dol/src/dol/datamodel/mapping/package.html @@ -0,0 +1,20 @@ + + + + + + +Internal data model for the mapping representation. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/datamodel/package.html b/dol/src/dol/datamodel/package.html new file mode 100644 index 0000000..398bc27 --- /dev/null +++ b/dol/src/dol/datamodel/package.html @@ -0,0 +1,20 @@ + + + + + + +Internal data model. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/datamodel/pn/Channel.java b/dol/src/dol/datamodel/pn/Channel.java new file mode 100644 index 0000000..2aa0706 --- /dev/null +++ b/dol/src/dol/datamodel/pn/Channel.java @@ -0,0 +1,129 @@ +/* $Id: Channel.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class is the basic channel in a process network. + * The channel has a name and a list of ports connected by this channel. + * In this model, a channel has only two ports: one input port and one + * output port. + */ +public class Channel extends Resource implements Schedulable { + + /** + * Constructor to create a channel with a name and an empty + * portList. + */ + public Channel(String name) { + super(name); + } + + /** + * Accept a visitor. + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this channel. + * + * @return new instance of the channel + */ + public Object clone() { + Channel newObj = (Channel) super.clone(); + return (newObj); + } + + /** + * Get the size of this channel. + * + * @return size of the channel + */ + public int getSize() { + return _size; + } + + /** + * Set the size of this channel. + * + * @param size new size value + */ + public void setSize(int size) { + _size = size; + } + + /** + * Get the token size of this channel. + * + * @return token size of the channel + */ + public int getTokenSize() { + return _tokenSize; + } + + /** + * Set the token size of this channel. + * + * @param size new token size + */ + public void setTokenSize(int size) { + _tokenSize = size; + } + + /** + * Return a string representation of the channel. + * + * @return string representation of the channel + */ + public String toString() { + return "Channel: " + getName(); + } + + /** + * Get the process where this channel starts from. + * + * @return origin process + */ + public Process getOrigin() { + return _origin; + } + + /** + * Set the process where this channel starts from. + * + * @param origin origin process + */ + public void setOrigin(Process origin) { + _origin = origin; + } + + /** + * Get the process where this channel ends. + * + * @return target process + */ + public Process getTarget() { + return _target; + } + + /** + * Set the process where this channel ends. + * + * @param target target process + */ + public void setTarget(Process target) { + _target = target; + } + + protected Process _origin; + protected Process _target; + + /** size of the channel */ + protected int _size; + + /** token size fo the channel */ + protected int _tokenSize; +} diff --git a/dol/src/dol/datamodel/pn/Configuration.java b/dol/src/dol/datamodel/pn/Configuration.java new file mode 100644 index 0000000..153173d --- /dev/null +++ b/dol/src/dol/datamodel/pn/Configuration.java @@ -0,0 +1,112 @@ +/* $Id: Configuration.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class represents a name-value pair of a configuration tag in XML. + */ +public class Configuration { + + /** + * Constructor to create a Configuration. + */ + public Configuration(String name) { + _name = name; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Configuration. + * + * @return new instance of the Configuration. + */ + public Object clone() { + try { + Configuration newObj = (Configuration) super.clone(); + newObj.setName(_name); + newObj.setValue(_value); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Configuration. + * + * @return the value of the configuration. + */ + public String getValue() { + return _value; + } + + /** + * Set the value of the Configuration. + * + * @param value the value of the configuration. + */ + public void setValue(String value) { + _value = value; + } + + /** + * Get the name of this configuration. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this configuration. + * + * @param name name of the configuration + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the hierarchical parent of this resource. + * + * @return parent of this resource + */ + public Resource getParentResource() { + return _parentResource; + } + + + /** + * Set the hierarchical parent of this resource. + * + * @param parentResource new parent + */ + public void setParentResource(Resource parentResource) { + _parentResource = parentResource; + } + + + /** + * Return a string representation of the Configuration. + * + * @return string representation of the Configuration + */ + public String toString() { + return "Configuration: " + getName(); + } + + protected String _value = null; + protected String _name = null; + protected Resource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/pn/Connection.java b/dol/src/dol/datamodel/pn/Connection.java new file mode 100644 index 0000000..3830f96 --- /dev/null +++ b/dol/src/dol/datamodel/pn/Connection.java @@ -0,0 +1,130 @@ +/* $Id: Connection.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class defines a connection. + * A connection contains one origin and one target resource and the + * according ports. + */ +public class Connection extends Resource { + + /** + * Constructor to create a Connection with a name, + * empty process list and empty channel list. + */ + public Connection(String name) { + super(name); + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Connection. + * + * @return a new instance of the Connection. + */ + public Object clone() { + Connection newObj = (Connection) super.clone(); + newObj.setOrigin(_origin); + newObj.setOriginPort(_originPort); + newObj.setTarget(_target); + newObj.setTargetPort(_targetPort); + return (newObj); + } + + /** + * Return a string representation of the connection. + * + * @return string representation of the connection + */ + public String toString() { + return "Connection: " + getName(); + } + + + /** + * Get the origin resource of his connection. + * + * @return origin resource + */ + public Resource getOrigin() { + return _origin; + } + + /** + * Set the origin resource of this connection. + * + * @param origin origin resource + */ + public void setOrigin(Resource origin) { + _origin = origin; + } + + /** + * Get the origin port of this connection. + * + * @return origin port + */ + public Port getOriginPort() { + return _originPort; + } + + /** + * Set the origin port of this connection. + * + * @param port origin port + */ + public void setOriginPort(Port port) { + _originPort = port; + } + + /** + * Get the target resource of his connection. + * + * @return target resource + */ + public Resource getTarget() { + return _target; + } + + /** + * Set the target resource of this connection. + * + * @param target target resource + */ + public void setTarget(Resource target) { + _target = target; + } + + /** + * Get the target port of this connection. + * + * @return target port + */ + public Port getTargetPort() { + return _targetPort; + } + + /** + * Set the target port of this connection. + * + * @param port target port + */ + public void setTargetPort(Port port) { + _targetPort = port; + } + + protected Resource _origin; + protected Port _originPort; + protected Resource _target; + protected Port _targetPort; +} diff --git a/dol/src/dol/datamodel/pn/Port.java b/dol/src/dol/datamodel/pn/Port.java new file mode 100644 index 0000000..e82e46e --- /dev/null +++ b/dol/src/dol/datamodel/pn/Port.java @@ -0,0 +1,189 @@ +/* $Id: Port.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class represents a port of a process or channel. + */ +public class Port { + + /** + * Constructor to create a Port with a name. + */ + public Port(String name) { + _name = name; + _isInPort = false; + _isOutPort = false; + } + + public Port(String name, boolean type) { + _name = name; + _isInPort = (type == INPORT); + _isOutPort = (type == OUTPORT); + } + + + /** + * Accept a visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Port + * + * @return new instance of the Port + */ + public Object clone() { + try { + Port newObj = (Port) super.clone(); + newObj.setName(_name); + newObj.setBasename(_basename); + newObj.setPeerPort(_peerPort); + newObj.setPeerResource(_peerResource); + newObj.setResource(_resource ); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Check whether this port is an inport. + * + * @return true if this port is an inport, otherwise false + */ + public boolean isInPort() { + return _isInPort; + } + + /** + * Check whether this port is an outport. + * + * @return true if this port is an outport, otherwise false + */ + public boolean isOutPort() { + return _isOutPort; + } + + /** + * Get the name of this port. + * + * @return name of the port + */ + public String getName() { + return _name; + } + + /** + * Set the name of this port. + * + * @param name name of the port + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the range of this port. + * + * @return range of the port + */ + public String getRange() { + return _range; + } + + /** + * Set the range of this port. + * + * @param range range of the port + */ + public void setRange(String range) { + _range= range; + } + + /** + * Get the resource of this port. + * + * @return the resource + */ + public Resource getResource() { + return _resource; + } + + /** + * Set the process of this port. + * + * @param resource The new resource + */ + public void setResource(Resource resource) { + _resource = resource; + } + + public void setBasename(String basename) { _basename = basename; } + public String getBasename() { return _basename; } + + // for channel port + public void setPeerPort(Port peer) { _peerPort = peer; } + public Port getPeerPort() { return _peerPort; } + + // for process port: peer channel name + public void setPeerResource(Resource n) { _peerResource = n; } + public Resource getPeerResource() { return _peerResource; } + + public String getType() { + if (_isInPort) return "input"; + else if (_isOutPort) return "output"; + else return ""; + } + + /** + * Return a string representation of the port. + * + * @return string representation of the port + */ + public String toString() { + return "Port: " + _name; + } + + /** constant for inport for usage in the constructor **/ + public static final boolean INPORT = true; + + /** constant for outport for usage in the constructor **/ + public static final boolean OUTPORT = false; + + /** defines whether this port is an inport **/ + protected boolean _isInPort = false; + + /** defines whether this port is an outport **/ + protected boolean _isOutPort = false; + + /** name of the port */ + protected String _name = null; + + /** basename of the resource, if no basename, store the name */ + protected String _basename = null; + + /** resource (process or channel) this port belongs to */ + protected Resource _resource = null; + + /** resource which peerPort belongs to */ + protected Resource _peerResource = null; + + /** connected peer port name */ + protected Port _peerPort = null; + + /** peer channel name */ + protected String _channelName = null; + + /** + * Range of the iterator when the instance belongs to an iterated + * series of ports. + */ + protected String _range = ""; +} diff --git a/dol/src/dol/datamodel/pn/Process.java b/dol/src/dol/datamodel/pn/Process.java new file mode 100644 index 0000000..1b825b4 --- /dev/null +++ b/dol/src/dol/datamodel/pn/Process.java @@ -0,0 +1,150 @@ +/* $Id: Process.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import java.util.StringTokenizer; +import java.util.Vector; + +import dol.datamodel.architecture.Processor; +import dol.visitor.PNVisitor; + +/** + * This class represents a process. + */ +public class Process extends Resource implements Schedulable { + + /** + * Constructor to create a Process with a name and an empty + * portList. + */ + public Process(String name) { + super(name); + } + + /** + * Accept a Visitor + * @param x A Visitor Object. + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Process + * + * @return a new instance of the Process. + */ + public Object clone() { + Process newObj = (Process) super.clone(); + newObj.setRange(_range); + newObj.setProcessor(_processor); + return (newObj); + } + + /** + * Get the range of this process. + * + * @return range + */ + public String getRange() { + return _range; + } + + /** + * Get the iterator indices of this process. + * + * @return range + */ + public Vector getIteratorIndices() { + Vector indices = new Vector(); + StringTokenizer tokenizer = + new StringTokenizer(_name.replaceAll(_basename, ""), "_"); + while (tokenizer.hasMoreTokens()) { + indices.add(Integer.valueOf(tokenizer.nextToken())); + } + return indices; + } + + /** + * Set the range of this process. + * + * @param range new range value + */ + public void setRange(String range) { + _range= range; + } + + public boolean hasInPorts() { + for (Port port : getPortList()) { + if (port.isInPort()) + return true; + } + return false; + } + + public boolean hasOutPorts() { + for (Port port : getPortList()) { + if (port.isOutPort()) + return true; + } + return false; + } + + /** + * Get number of inport + */ + public int getNumOfInports() { + int i = 0; + for (Port port : getPortList()) + if (port.isInPort()) + i++; + return i; + } + + /** + * Get number of outport + */ + public int getNumOfOutports() { + int i = 0; + for (Port port : getPortList()) + if (port.isOutPort()) + i++; + return i; + } + + /** + * Set the processor this process runs on. + * @param processor + */ + public void setProcessor(Processor processor) + { + _processor = processor; + } + + /** + * Get the processor this process runs on. + */ + public Processor getProcessor() + { + return _processor; + } + + /** + * Return a description of the process. + * + * @return a description of the process. + */ + public String toString() { + return "Process: " + getName(); + } + + /** + * Range of the iterator when the instance belongs to an iterated + * series of processes. + */ + protected String _range = ""; + + /** + * Processor that executes this process. + */ + protected Processor _processor = null; +} diff --git a/dol/src/dol/datamodel/pn/ProcessNetwork.java b/dol/src/dol/datamodel/pn/ProcessNetwork.java new file mode 100644 index 0000000..b41facc --- /dev/null +++ b/dol/src/dol/datamodel/pn/ProcessNetwork.java @@ -0,0 +1,189 @@ +/* $Id: ProcessNetwork.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import java.util.Vector; + +import dol.visitor.PNVisitor; + + +/** + * This class defines a process network. + */ +public class ProcessNetwork extends Resource { + + /** + * Constructor to create a process network with a name, + * empty process list and empty channel list. + */ + public ProcessNetwork(String name) { + super(name); + _processList = new Vector(); + _channelList = new Vector(); + _varList = new Vector(); + _connectionList = new Vector(); + } + + /** + * Accept a visitor + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this ProcessNetwork. + * + * @return new instance of the ProcessNetwork + */ + @SuppressWarnings("unchecked") + public Object clone() { + ProcessNetwork newObj = (ProcessNetwork) super.clone(); + newObj.setProcessList((Vector)_processList.clone()); + newObj.setChannelList((Vector)_channelList.clone()); + newObj.setVarList((Vector)_varList.clone()); + newObj.setConnectionList((Vector)_connectionList.clone()); + return (newObj); + } + + /** + * Get the process list of a ProcessNetwork. + * + * @return the process list + */ + public Vector getProcessList() { + return _processList; + } + + /** + * Get the process basenames. + * + * @return process basenames + */ + public Vector getProcessBasenames() { + Vector basenames = new Vector(); + for (Process p : _processList) { + String basename = p.getBasename(); + if (!basenames.contains(basename)) { + basenames.add(basename); + } + } + return basenames; + } + + /** + * Set the process list of a ProcessNetwork. + * + * @param processList The new list + */ + public void setProcessList(Vector processList) { + _processList = processList; + } + + /** + * Get the channel list of a ProcessNetwork + * + * @return the channel list + */ + public Vector getChannelList() { + return _channelList; + } + + /** + * Set the channel list of a ProcessNetwork + * + * @param channelList The new list + */ + public void setChannelList(Vector channelList) { + _channelList = channelList; + } + + public Vector getVarList() { return _varList; } + public void setVarList(Vector list) { _varList = list; } + + public void setConnectionList(Vector list) { _connectionList = list; } + public Vector getConnectionList() { return _connectionList; } + + + /** + * Return a description of the ProcessNetwork. + * + * @return a description of the ProcessNetwork. + */ + public String toString() { + return "ProcessNetwork: " + getName(); + } + + /** + * Return a process which has a specific name. Return null if + * process cannot be found. + * + * @param name the name of the process to search for. + * @return the process with the specific name. + */ + public Process getProcess(String name) { + for (Process process : _processList) { + if (process.getName().equals(name)) { + return process; + } + } + return null; + } + + /** + * Return a channel which has a specific name. Return null if + * not found. + * + * @param name the name of the channel to search for. + * @return the channel with the specific name. + */ + public Channel getChannel(String name) { + for (Channel c : _channelList) { + if (c.getName().equals(name)) { + return c; + } + } + return null; + } + + /** + * In this version, we only deal with the flattened process network. + * Connection infomation is filled to each process and channel for fast + * iteration. + */ + public void fillPortPeerInfo() throws Exception { + for (Connection c : getConnectionList()) { + Port originPort = c.getOriginPort(); + Port targetPort = c.getTargetPort(); + originPort.setPeerResource(c.getTarget()); + originPort.setPeerPort(targetPort); + targetPort.setPeerResource(c.getOrigin()); + targetPort.setPeerPort(originPort); + } + + //for each sw channel, determine which processes it connects + for (Connection c : getConnectionList()) { + if (c.getOrigin() instanceof Process) { + Channel channel = (Channel)c.getTarget(); + channel.setOrigin((Process)c.getOrigin()); + } + else if (c.getOrigin() instanceof Channel) { + Channel channel = (Channel)c.getOrigin(); + channel.setTarget((Process)c.getTarget()); + } + } + } + + /** list of the processes in the ProcessNetwork. */ + protected Vector _processList = null; + + /** list of the channels in the ProcessNetwork. */ + protected Vector _channelList = null; + + /** list of variables in the ProcessNetwork */ + protected Vector _varList = null; + + /** list of connections in the ProcessNetwork */ + protected Vector _connectionList = null; +} diff --git a/dol/src/dol/datamodel/pn/ProfilingConfiguration.java b/dol/src/dol/datamodel/pn/ProfilingConfiguration.java new file mode 100644 index 0000000..a7c4c73 --- /dev/null +++ b/dol/src/dol/datamodel/pn/ProfilingConfiguration.java @@ -0,0 +1,34 @@ +/* $Id: ProfilingConfiguration.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +public class ProfilingConfiguration extends Configuration { + + /** + * Constructor to create a ProfilerConfiguration. + */ + public ProfilingConfiguration(String name) { + super(name); + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Configuration. + * + * @return new instance of the Configuration. + */ + public Object clone() { + ProfilingConfiguration newObj = (ProfilingConfiguration) super.clone(); + return newObj; + } + +} diff --git a/dol/src/dol/datamodel/pn/Resource.java b/dol/src/dol/datamodel/pn/Resource.java new file mode 100644 index 0000000..d212bbe --- /dev/null +++ b/dol/src/dol/datamodel/pn/Resource.java @@ -0,0 +1,269 @@ +/* $Id: Resource.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import java.util.Vector; + +import dol.visitor.PNVisitor; + +/** + * This class is the basic class which abstracts a resource of a generic + * process network. The resource has a name and a list of ports. + */ +public class Resource { + + /** + * Constructor to create a resource with a name and an empty + * portList. + */ + public Resource(String name) { + _name = name; + _basename = name; + _portList = new Vector(); + _srcList = new Vector(); + _cfgList = new Vector(); + _profilingList = new Vector(); + } + + /** + * Accept a Visitor + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this resource. + * + * @return new instance of the resource. + */ + @SuppressWarnings("unchecked") + public Object clone() { + try { + Resource newObj = (Resource) super.clone(); + newObj.setName(_name); + newObj.setType(_type); + newObj.setBasename(_basename); + newObj.setPortList((Vector)_portList.clone() ); + newObj.setSrcList((Vector)_srcList.clone() ); + newObj.setCfgList((Vector)_cfgList.clone() ); + newObj.setProfilingList((Vector)_profilingList.clone() ); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + public String getBasename() { + return _basename; + } + + public void setBasename(String basename) { + _basename = basename; + } + + /** + * Get the name of this resource. + * + * @return name of the resource + */ + public String getName() { + return _name; + } + + /** + * Set the name of this resource. + * + * @param name name of the resource + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the type of this resource. + * + * @return type of the resource + */ + public String getType() { + return _type; + } + + /** + * Set the type of this resource. + * + * @param type type of the resource + */ + public void setType(String type) { + _type = type; + } + + /** + * Get the list of source codes of this resource. + * + * @return list of source codes + */ + public Vector getSrcList() { + return _srcList; + } + + /** + * Get the list of ports of this resource. + * + * @return list of ports + */ + public Vector getPortList() { + return _portList; + } + + /** + * Set the list of ports of this resource. + * + * @param portList port list + */ + public void setPortList(Vector portList) { + _portList = portList; + } + + /** + * Get the list of configurations of this resource. + * + * @return list of configurations + */ + public Vector getCfgList() { + return _cfgList; + } + + /** + * Set the list of configurations of this resource. + * + * @param cfgList configuration list + */ + public void setCfgList(Vector cfgList) { + _cfgList = cfgList; + } + + /** + * Get the list of profiling info of this resource. + * + * @return list of profiling info + */ + public Vector getProfilingList() { + return _profilingList; + } + + /** + * Set the list of profiling info of this resource. + * + * @param cfgList profiling info list + */ + public void setProfilingList(Vector cfgList) { + _profilingList = cfgList; + } + + /** + * Return a profiling which has a specific name. Return null when it + * cannot be found. + * + * @param name name of the profiling to search for + * @return profiling with the specified name + */ + public ProfilingConfiguration getProfilingCfg(String name) { + for (ProfilingConfiguration cfg : _profilingList) { + if (cfg.getName().equals(name)) { + return cfg; + } + } + return null; + } + + /** + * Set the list of source code of this resource. + * + * @param srcList port list + */ + public void setSrcList(Vector srcList) { + _srcList = srcList; + } + + /** + * Get the hierarchical parent of this resource. + * + * @return parent of this resource + */ + public Resource getParentResource() { + return _parentResource; + } + + /** + * Set the hierarchical parent of this resource. + * + * @param parentResource new parent + */ + public void setParentResource(Resource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the resource. + * + * @return string representation of the resource + */ + public String toString() { + return "Resource: " + _name; + } + + /** + * Return a port which has a specific name. Return null when port + * cannot be found. + * + * @param name name of the port to search for + * @return port with the specified name + */ + public Port getPort(String name) { + for (Port port : _portList) { + if (port.getName().equals(name)) { + return port; + } + } + return null; + } + + /** + * Return a port which has a specific name. Return null when port + * cannot be found. + * + * @return port with the specified name + */ + public Port getFirstPort() { + return (Port) _portList.firstElement(); + } + + /** name of the resource */ + protected String _name = null; + + /** type of the resource */ + protected String _type = null; + + /** basename of the resource, if no basename, store the name */ + protected String _basename = null; + + /** list of the ports of the Resource */ + protected Vector _portList = null; + + /** list of the source codes of the Resource */ + protected Vector _srcList = null; + + /** list of the configurations of the Resource */ + protected Vector _cfgList = null; + + /** list of the profiling info of the Resource */ + protected Vector _profilingList = null; + + /** + * parent resource of this resource in a hierarchical process network + */ + protected Resource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/pn/Schedulable.java b/dol/src/dol/datamodel/pn/Schedulable.java new file mode 100644 index 0000000..4e5336c --- /dev/null +++ b/dol/src/dol/datamodel/pn/Schedulable.java @@ -0,0 +1,10 @@ +/* $Id: Schedulable.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +/** + * Marker interface for all entities that can be added + * to a scheduler table. + */ +public interface Schedulable { + +} diff --git a/dol/src/dol/datamodel/pn/SourceCode.java b/dol/src/dol/datamodel/pn/SourceCode.java new file mode 100644 index 0000000..26f09e1 --- /dev/null +++ b/dol/src/dol/datamodel/pn/SourceCode.java @@ -0,0 +1,131 @@ +/* $Id: SourceCode.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class is the basic source code of a process. + */ +public class SourceCode { + + /** + * Constructor to create a SourceCode with a name. + */ + public SourceCode(String name) { + _name = name; + } + + /** + * Accept a visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this SourceCode. + * + * @return new instance of the SourceCode. + */ + public Object clone() { + try { + SourceCode newObj = (SourceCode) super.clone(); + newObj.setName(_name); + newObj.setType(_type); + newObj.setLocality(_locality); + newObj.setProcess( (Process) _process.clone() ); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the name of this SourceCode. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this SourceCode. + * + * @param name name of the SourceCode + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the process of this SourceCode. + * + * @return process to which this SourceCode belongs + */ + public Process getProcess() { + return _process; + } + + /** + * Set the proces of this SourceCode. + * + * @param process process to which this SourceCode belongs + */ + public void setProcess(Process process) { + _process = process; + } + + /** + * Get the locality of this SourceCode. + * + * @return locality of the SourceCode + */ + public String getLocality() { + return _locality; + } + + /** + * Set the locality of this SourceCode. + * + * @param locality locality of the SourceCode + */ + public void setLocality(String locality) { + _locality = locality; + } + + /** + * Get the type of this SourceCode. + * + * @return type of the SourceCode + */ + public String getType() { + return _type; + } + + /** + * Set the type of this SourceCode. + * + * @param type The new type + */ + public void setType(String type) { + _type = type; + } + + /** + * Return a string representation of the SourceCode. + * + * @return string representation of the SourceCode + */ + public String toString() { + return "SourceCode: " + _name; + } + + protected String _name = null; + protected Process _process = null; + protected String _type = null; + protected String _locality = null; +} diff --git a/dol/src/dol/datamodel/pn/Variable.java b/dol/src/dol/datamodel/pn/Variable.java new file mode 100644 index 0000000..623a25c --- /dev/null +++ b/dol/src/dol/datamodel/pn/Variable.java @@ -0,0 +1,109 @@ +/* $Id: Variable.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.datamodel.pn; + +import dol.visitor.PNVisitor; + +/** + * This class represents a global variable in XML. + */ +public class Variable { + + /** + * Constructor to create a Variable. + */ + public Variable(String name) { + _name = name; + } + + /** + * Accept a Visitor. + * + * @param x visitor object + */ + public void accept(PNVisitor x) { + x.visitComponent(this); + } + + /** + * Clone this Variable. + * + * @return new instance of the Variable. + */ + public Object clone() { + try { + Variable newObj = (Variable) super.clone(); + newObj.setName(_name); + return (newObj); + } catch (CloneNotSupportedException e) { + System.out.println("Error Clone not Supported"); + } + return null; + } + + /** + * Get the value of the Variable. + * + * @return the value of the variable + */ + public int getValue() { + return _value; + } + + /** + * Set the value of the Variable. + * + * @param value the value of the variable + */ + public void setValue(int value) { + _value = value; + } + + /** + * Get the name of this SourceCode. + * + * @return the name + */ + public String getName() { + return _name; + } + + /** + * Set the name of this SourceCode. + * + * @param name name of the SourceCode + */ + public void setName(String name) { + _name = name; + } + + /** + * Get the hierarchical parent of this resource. + * + * @return parent of this resource + */ + public Resource getParentResource() { + return _parentResource; + } + + /** + * Set the hierarchical parent of this resource. + * + * @param parentResource new parent + */ + public void setParentResource(Resource parentResource) { + _parentResource = parentResource; + } + + /** + * Return a string representation of the Variable. + * + * @return string representation of the Variable + */ + public String toString() { + return "Variable: " + getName(); + } + + protected int _value; + protected String _name = null; + protected Resource _parentResource = null; +} diff --git a/dol/src/dol/datamodel/pn/package.html b/dol/src/dol/datamodel/pn/package.html new file mode 100644 index 0000000..1151360 --- /dev/null +++ b/dol/src/dol/datamodel/pn/package.html @@ -0,0 +1,20 @@ + + + + + + +Internal data model for the process network representation. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/helper/flattener/ArchFlattener.java b/dol/src/dol/helper/flattener/ArchFlattener.java new file mode 100644 index 0000000..c011d43 --- /dev/null +++ b/dol/src/dol/helper/flattener/ArchFlattener.java @@ -0,0 +1,281 @@ +/* $Id: ArchFlattener.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.util.List; + +import org.jdom.Element; +import org.jdom.Namespace; + +/** + * + */ +public class ArchFlattener extends FlattenerHelper { + + /** + * Constructor. + * + * @param classname class name of the generated class + */ + public ArchFlattener(String classname) { + super(classname); + } + + public String processElement(Element element) + throws RuntimeException { + + String string = ""; + + if (element.getName().equalsIgnoreCase("processor")) { + _generateElement = true; + string = generateProcessor(element); + } + else if (element.getName().equalsIgnoreCase("hw_channel")) { + _generateElement = true; + string = generateLink(element); + } + else if (element.getName().equalsIgnoreCase("memory")) { + _generateElement = true; + string = generateMemory(element); + } + else if (element.getName().equalsIgnoreCase("node")) { + _generateElement = true; + string = generateNode(element); + } + + if (element.getName().equalsIgnoreCase("connection")) { + _generateElement = false; + string = generateConnection(element); + } + + //belongs to mapping! + if (element.getName().equalsIgnoreCase("binding")) { + string = generateBinding(element); + } + return string; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateProcessor(Element element) { + String attributes[] = {"name", "type", "basename"}; + String sourceCode = generateElement(element, attributes, false); + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("iterator")) { + sourceCode += generateIterator(childElement); + } + else if (childElement.getName().equalsIgnoreCase("node")) { + sourceCode += generateNode(childElement); + } + else if (childElement.getName().equalsIgnoreCase("configuration")) { + sourceCode += generateConfiguration(childElement); + } + + } + sourceCode += _indent + "System.out.println(\"
\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateMemory(Element element) { + String attributes[] = {"name", "type", "basename"}; + String sourceCode = generateElement(element, attributes, false); + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("iterator")) { + sourceCode += generateIterator(childElement); + } + else if (childElement.getName().equalsIgnoreCase("node")) { + sourceCode += generateNode(childElement); + } + else if (childElement.getName().equalsIgnoreCase("configuration")) { + sourceCode += generateConfiguration(childElement); + } + } + sourceCode += _indent + "System.out.println(\"
\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateLink(Element element) { + String attributes[] = {"name", "type", "basename"}; + String sourceCode = generateElement(element, attributes, false); + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("iterator")) { + sourceCode += generateIterator(childElement); + } + else if (childElement.getName().equalsIgnoreCase("node")) { + sourceCode += generateNode(childElement); + } + else if (childElement.getName().equalsIgnoreCase("configuration")) { + sourceCode += generateConfiguration(childElement); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + protected String generateConfiguration(Element element) { + String attributes[] = {"name", "value"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, true); + decreaseXmlIndent(); + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateNode(Element element) { + increaseXmlIndent(); + + String sourceCode = ""; + if (_generateElement) { + String attributes[] = {"name", "basename"}; + sourceCode = generateElement(element, attributes, false); + } + else { + String attributes[] = {"name"}; + sourceCode = generateElement(element, attributes, false); + } + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("duplexport")) { + sourceCode += generatePort(childElement); + } + else if (childElement.getName().equalsIgnoreCase("inputport")) { + sourceCode += generatePort(childElement); + } + else if (childElement.getName().equalsIgnoreCase("outputport")) { + sourceCode += generatePort(childElement); + } + else if (childElement.getName().equalsIgnoreCase("port")) { + sourceCode += generatePort(childElement); + } + } + sourceCode += _indent + "System.out.println(\"" + _xmlIndent + + "
\");\n"; + + decreaseXmlIndent(); + return sourceCode; + } + + protected String generatePort(Element element) { + increaseXmlIndent(); + String sourceCode = ""; + if (_generateElement) { + String attributes[] = {"name", "basename"}; + sourceCode = generateElement(element, attributes, true); + } + else { + String attributes[] = {"name"}; + sourceCode = generateElement(element, attributes, true); + } + decreaseXmlIndent(); + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateConnection(Element element) { + String attributes[] = {"name"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin")) { + //origin found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns= childElement.getNamespace(); + Element nodeElement = childElement.getChild("node", ns); + if (nodeElement != null) { + sourceCode += generateNode(nodeElement); + } + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + } + else if (childElement.getName().equalsIgnoreCase("target")) { + //target found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns= childElement.getNamespace(); + Element nodeElement = childElement.getChild("node", ns); + if (nodeElement != null) { + sourceCode += generateNode(nodeElement); + } + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateBinding(Element element) { + String attributes[] = {"name", "parameter", "type"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin")) { + //origin found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns = childElement.getNamespace(); + Element portElement = childElement.getChild("port", ns); + if (portElement != null) { + increaseXmlIndent(); + String[] portAttributes = {"name", "basename"}; + sourceCode += generateElement(portElement, + portAttributes, true); + decreaseXmlIndent(); + } + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + } + else if (childElement.getName().equalsIgnoreCase("target")) { + //target found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns = childElement.getNamespace(); + Element portElement = childElement.getChild("port", ns); + if (portElement != null) { + increaseXmlIndent(); + String[] portAttributes = {"name", "basename"}; + sourceCode += generateElement(portElement, + portAttributes, true); + decreaseXmlIndent(); + } + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } +} diff --git a/dol/src/dol/helper/flattener/BugCatcher.java b/dol/src/dol/helper/flattener/BugCatcher.java new file mode 100644 index 0000000..30fa28f --- /dev/null +++ b/dol/src/dol/helper/flattener/BugCatcher.java @@ -0,0 +1,26 @@ +/* $Id: BugCatcher.java 1 2010-02-24 13:03:05Z haidw $ */ + +package dol.helper.flattener; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXParseException; + +/** + * + */ +class BugCatcher implements ErrorHandler { + public void error(SAXParseException ex) { + System.out.println("[ERROR ]: "+ex.getMessage()); + + } + + public void fatalError(SAXParseException ex) { + System.out.println("[PIZDEC ]: "+ex.getMessage()); + + } + + public void warning(SAXParseException ex) { + System.out.println("[WARNING]: "+ex.getMessage()); + + } +} diff --git a/dol/src/dol/helper/flattener/DomDocumentParser.java b/dol/src/dol/helper/flattener/DomDocumentParser.java new file mode 100644 index 0000000..e67a896 --- /dev/null +++ b/dol/src/dol/helper/flattener/DomDocumentParser.java @@ -0,0 +1,41 @@ +/* $Id: DomDocumentParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import org.apache.xerces.parsers.DOMParser; +import org.w3c.dom.Document; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; + +/** + * + */ +public class DomDocumentParser { + + DOMParser dp = null; + public ErrorHandler bc = new BugCatcher(); + + public DomDocumentParser() { + dp = new DOMParser(); + dp.setErrorHandler(bc); + } + + public Document parseDocument(File file){ + try { + InputStream is = new FileInputStream(file); + InputSource iss = new InputSource(is); + dp.parse(iss); + return dp.getDocument(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } + + public Document parseDocument(String pathToFile){ + return parseDocument(new File(pathToFile)); + } +} diff --git a/dol/src/dol/helper/flattener/FlattenerHelper.java b/dol/src/dol/helper/flattener/FlattenerHelper.java new file mode 100644 index 0000000..b11d977 --- /dev/null +++ b/dol/src/dol/helper/flattener/FlattenerHelper.java @@ -0,0 +1,314 @@ +/* $Id: FlattenerHelper.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.util.List; + +import org.jdom.Document; +import org.jdom.Element; + +import dol.datamodel.XmlTag; + +/** + * Helper class to generate the source code of a class for writing + * an XML file based on an XML file with iterators. For details, refer + * to removeIterators(). + * + * @see #removeIterators(org.jdom.Document doc) + */ +public class FlattenerHelper { + + protected String _preamble = "public class Generator {\n\n public " + + "Generator() {\n //nothing to be done here\n }\n" + + "\n public static void main(String[] args) {\n"; + + + //true, when an element is generated. places a restriction on the append + //element, namely that it must be a single variable, no function of a + //variable + protected boolean _generateElement = false; + + protected String _indent = ""; //indentation of lines in Generator class + + //code for obtaining range of iterator variables. is updated in each + //call of generateAppend + protected String _iteratorRangeCode = ""; + + //indentation of XML elements (use increaseXmlIndent() and + //decreaseXmlIndent() to change the indentation) + protected String _xmlIndent = ""; + + /** + * Default constructor. The generated class will have the name + * "Generator". + */ + public FlattenerHelper() { + } + + /** + * Constructor. + * + * @param classname class name of the generated class + */ + public FlattenerHelper(String classname) { + _preamble = _preamble.replace("Generator", classname); + } + + public String processElement(Element element) { + return ""; + } + + /** + * Generate source code for println statement that prints out an XML + * element. + * @param element the XML element itself + * @param attributes attributes to include in the XML element + * @param standalone true, when element shall be closed. false, otherwise + * @return println statement which prints out the XML element + */ + protected String generateElement(Element element, + String[] attributes, boolean standalone) + { + String sourceCode = _indent + "System.out.println(\""; + sourceCode += _xmlIndent + "<" + element.getName(); + for (int i = 0; i < attributes.length; i++) { + + if (attributes[i].equals("basename")) { + sourceCode += " " + attributes[i] + "=\\\""; + sourceCode += element.getAttributeValue("name"); + + if (!generateAppend(element).equals("")) { + sourceCode += "\\\""; + sourceCode += " range=\\\"" + _iteratorRangeCode; + } + sourceCode += "\\\""; + } else if (attributes[i].equals("name")) { + sourceCode += " " + attributes[i] + "=\\\""; + sourceCode += element.getAttributeValue("name"); + sourceCode += generateAppend(element); + sourceCode += "\\\""; + } else if (attributes[i].equals("size") + && element.getName().equals(_xt.getSWChannelTag())) { + sourceCode += " " + attributes[i] + "=\\\""; + sourceCode += "\" + "; + sourceCode += element.getAttributeValue("size"); + sourceCode += "+ \""; + sourceCode += "\\\""; + } else if (attributes[i].equals("tokensize") + && element.getName().equals(_xt.getSWChannelTag())) { + if (element.getAttributeValue("tokensize") != null) { + sourceCode += " " + attributes[i] + "=\\\""; + sourceCode += "\""; + sourceCode += " + "; + sourceCode += element.getAttributeValue("tokensize"); + sourceCode += "+ \""; + sourceCode += "\\\""; + } + } else { + sourceCode += " " + attributes[i] + "=\\\""; + sourceCode += element.getAttributeValue(attributes[i]); + sourceCode += "\\\""; + } + + } + + if (standalone) + sourceCode += "/"; + sourceCode +=">\");\n"; + + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + public String removeIterators(Document doc) + throws RuntimeException { + String sourceCode = _preamble; + String functionString = ""; + String rootName = ""; + _indent = " "; + + Element root = doc.getRootElement(); + String xmlns = root.getNamespaceURI(); + String xsiLocation = ""; + for (org.jdom.Attribute attr : + (List)root.getAttributes()) { + //System.out.println(attr.getName() + ": "); + //System.out.println(attr.getValue()); + if (attr.getName().equals("schemaLocation")) + xsiLocation = attr.getValue(); + } + + if (!root.getChildren().isEmpty()) { + //if there are any elements in the document, create a new header + //for the xml document + rootName = root.getName(); + + sourceCode += _indent + + "java.util.Hashtable table = " + + "new java.util.Hashtable();\n"; + + sourceCode += _indent + "System.out.println(\"" + + "\");\n"; + sourceCode += _indent + "System.out.println(\"<" + + rootName + " xmlns=\\\"" + xmlns + + "\\\" xmlns:xsi=\\\"http://www.w3.org/2001/" + + "XMLSchema-instance\\\" \\n xsi:schemaLocation=\\\"" + + xsiLocation + "\\\" name=\\\"" + + root.getAttributeValue("name") + "_flattened" + + "\\\">\\n \");\n"; + } + + + for (Element elmt : (List)root.getChildren()) { + try { + if (elmt.getName().equalsIgnoreCase(_xt.getIteratorTag())) { + sourceCode += generateIterator(elmt); + } + else if (elmt.getName().equalsIgnoreCase(_xt.getVariableTag())) { + sourceCode += generateVariable(elmt); + } + else if (elmt.getName().equalsIgnoreCase(_xt.getFunctionTag())) { + functionString += generateFunction(elmt); + } + else { + sourceCode += processElement(elmt); + } + } + catch (RuntimeException e) { + String elementName = ""; + System.out.println(e.getMessage()); + if (!elmt.getName().equalsIgnoreCase(_xt.getIteratorTag()) && + !elmt.getName().equalsIgnoreCase(_xt.getVariableTag())) { + elementName = elmt.getAttributeValue("name"); + } + throw new RuntimeException( + "Error: An error occurred while parsing the <" + + elmt.getName() + "> element \"" + + elementName + "\"."); + } + } + + sourceCode += _indent + "System.out.println(\"\");\n }\n"; + _indent = " "; + sourceCode += functionString; + sourceCode += "\n}"; + + return sourceCode; + } + + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateIterator(Element element) { + String variableName = element.getAttributeValue("variable"); + /* + //extract the upper iteration boundary and construct the for loop + String forLoop = element.getAttributeValue("range"); + String boundary = forLoop.replaceAll( + "for.*;[ ]*\\w*[ ]*[<>=]* *(.*);.*", "$1"); + */ + String forLoop = "for (int " + variableName + " = 0; " + variableName + + " < " + element.getAttributeValue("range") + "; " + + variableName + "++)"; + + String sourceCode = _indent + "table.put(\"" + variableName + + "\", " + element.getAttributeValue("range") + ");\n"; + + sourceCode += _indent + forLoop + " {\n"; + _indent += " "; + + for (Element el : (List)element.getChildren()) { + try { + if (el.getName().equalsIgnoreCase(_xt.getIteratorTag())) { + sourceCode += generateIterator(el); + } + else { + sourceCode += processElement(el); + } + } + catch (RuntimeException e) { + System.out.println(e.getMessage()); + throw new RuntimeException( + "Error: An error occurred while parsing the <" + + el.getName() + "> element \"" + + el.getAttributeValue("name") + "\"."); + } + } + _indent = _indent.substring(0, _indent.length() - 2); + sourceCode += _indent + "}\n"; + sourceCode += _indent + "table.remove(\"" + variableName + "\");\n"; + + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateAppend(Element element) + throws RuntimeException { + String sourceCode = ""; + + _iteratorRangeCode = ""; + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("append")) { + if (_generateElement && + !childElement.getAttributeValue(_xt.getFunctionTag()).replaceAll( + "\\w", "").equals("")) { + throw new RuntimeException("Error: Found \"" + + childElement.getAttributeValue("function") + + "\" in . When constructing elements with an " + + "iterator using , xxx must " + + "be a single variable. No mathematical operations " + + "are permitted."); + } + sourceCode += "\" + \"_\" + (" + + childElement.getAttributeValue(_xt.getFunctionTag()) + + ") + \""; + + if (!_iteratorRangeCode.equals("")) + _iteratorRangeCode += ";"; + + _iteratorRangeCode += "\" + table.get(\"" + + childElement.getAttributeValue(_xt.getFunctionTag()) + + "\") + \""; + } + } + return sourceCode; + } + + protected String generateVariable(Element element) { + String sourceCode = _indent + "int "; + sourceCode += element.getAttributeValue("name"); + sourceCode += " = "; + sourceCode += element.getAttributeValue("value"); + sourceCode += ";\n"; + return sourceCode; + } + + protected String generateFunction(Element element) { + return element.getText(); + } + + /** + * Increase the XML indentation. + **/ + protected void increaseXmlIndent() { + _xmlIndent += " "; + } + + /** + * Decrease the XML indentation. + */ + protected void decreaseXmlIndent() { + _xmlIndent = _xmlIndent.substring(0, _xmlIndent.length() - 2); + } + + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/helper/flattener/MappingFlattener.java b/dol/src/dol/helper/flattener/MappingFlattener.java new file mode 100644 index 0000000..19ae0e7 --- /dev/null +++ b/dol/src/dol/helper/flattener/MappingFlattener.java @@ -0,0 +1,156 @@ +/* $Id: MappingFlattener.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.util.List; + +import org.jdom.Element; + +/** + * + */ +public class MappingFlattener extends FlattenerHelper { + + /** + * Constructor. + * + * @param classname class name of the generated class + */ + public MappingFlattener(String classname) { + super(classname); + } + + public String processElement(Element element) + throws RuntimeException { + + String string = ""; + + if (element.getName().equalsIgnoreCase("binding")) { + _generateElement = false; + string = generateBinding(element); + } + else if (element.getName().equalsIgnoreCase("path")) { + _generateElement = false; + string = generatePath(element); + } + else if (element.getName().equalsIgnoreCase("schedule")) { + _generateElement = false; + string = generateSchedule(element); + } + + return string; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generatePath(Element element) { + String attributes[] = {"name"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin") + || childElement.getName().equalsIgnoreCase("target")) { + sourceCode += generateOriginOrTarget(childElement); + } + else if (childElement.getName().equalsIgnoreCase("resource")) { + String rAttributes[] = {"name"}; + increaseXmlIndent(); + sourceCode += generateElement(childElement,rAttributes, false); + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "
\");\n"; + decreaseXmlIndent(); + } + else if (childElement.getName().equalsIgnoreCase("buffer")) { + String bAttributes[] = {"name"}; + increaseXmlIndent(); + sourceCode += generateElement(childElement,bAttributes, false); + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateSchedule(Element element) { + String attributes[] = {"name", "type"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin")) { + sourceCode += generateOriginOrTarget(childElement); + } + else if (childElement.getName().equalsIgnoreCase("resource")) { + String rAttributes[] = {"name"}; + increaseXmlIndent(); + sourceCode += generateElement(childElement,rAttributes, false); + sourceCode += _indent + + "System.out.println(\"" + _xmlIndent + "
\");\n"; + decreaseXmlIndent(); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateBinding(Element element) { + String attributes[] = {"name", "type"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin") + || childElement.getName().equalsIgnoreCase("target")) { + sourceCode += generateOriginOrTarget(childElement); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateOriginOrTarget(Element element) { + String attributes[] = {"name"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, false); + + //add configuration elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase(_xt.getConfigurationTag())) { + sourceCode += generateConfiguration(childElement); + } + } + + sourceCode += _indent + "System.out.println(\"" + _xmlIndent + "\");\n"; + decreaseXmlIndent(); + + return sourceCode; + } + + protected String generateConfiguration(Element element) { + String attributes[] = {"name", "value"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, true); + decreaseXmlIndent(); + return sourceCode; + } + + +} diff --git a/dol/src/dol/helper/flattener/PNFlattener.java b/dol/src/dol/helper/flattener/PNFlattener.java new file mode 100644 index 0000000..fc6af53 --- /dev/null +++ b/dol/src/dol/helper/flattener/PNFlattener.java @@ -0,0 +1,184 @@ +/* $Id: PNFlattener.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.util.List; + +import org.jdom.Element; +import org.jdom.Namespace; + +/** + * + */ +public class PNFlattener extends FlattenerHelper { + + /** + * Constructor. + * + * @param classname class name of the generated class + */ + public PNFlattener(String classname) { + super(classname); + } + + public String processElement(Element element) + throws RuntimeException { + + String string = ""; + + _generateElement = true; + if (element.getName().equalsIgnoreCase(_xt.getProcessTag())) { + string = generateProcess(element); + } else if (element.getName().equalsIgnoreCase(_xt.getSWChannelTag())) { + string = generateChannel(element); + } else if (element.getName().equalsIgnoreCase(_xt.getPortTag())) { + string = generatePort(element); + } else if (element.getName().equalsIgnoreCase(_xt.getConfigurationTag())) { + string = generateConfiguration(element); + } else if (element.getName().equalsIgnoreCase(_xt.getProfilingTag())) { + string = generateProfiling(element); + } + + _generateElement = false; + + if (element.getName().equalsIgnoreCase(_xt.getConnectionTag())) { + string = generateConnection(element); + } + return string; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateProcess(Element element) { + String attributes[] = {"name", "basename"}; + String sourceCode = generateElement(element, attributes, false); + + for (Element childElement : (List)element.getChildren()) { + if (!(childElement.getName().equalsIgnoreCase(_xt.getPortTag()) || + childElement.getName().equalsIgnoreCase(_xt.getSourceTag()) || + childElement.getName().equalsIgnoreCase(_xt.getConfigurationTag()) || + childElement.getName().equalsIgnoreCase(_xt.getProfilingTag()))) { + if (childElement.getName().equalsIgnoreCase(_xt.getIteratorTag())) { + sourceCode += generateIterator(childElement); + } + } else { + if (childElement.getName().equalsIgnoreCase(_xt.getPortTag())) { + sourceCode += generatePort(childElement); + } else if (childElement.getName().equalsIgnoreCase(_xt.getConfigurationTag())) { + sourceCode += generateConfiguration(childElement); + } else if (childElement.getName().equalsIgnoreCase(_xt.getProfilingTag())) { + sourceCode += generateProfiling(childElement); + } + else { + //source found + increaseXmlIndent(); + String[] sourceAttributes = {"location", "type"}; + sourceCode += generateElement(childElement, + sourceAttributes, true); + decreaseXmlIndent(); + } + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateChannel(Element element) { + String attributes[] = {"name", "type", "size", + "basename", "tokensize"}; + String sourceCode = generateElement(element, attributes, false); + + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase(_xt.getPortTag())) { + sourceCode += generatePort(childElement); + } else if (childElement.getName().equalsIgnoreCase(_xt.getConfigurationTag())) { + sourceCode += generateConfiguration(childElement); + } else if (childElement.getName().equalsIgnoreCase(_xt.getProfilingTag())) { + sourceCode += generateProfiling(childElement); + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } + + protected String generatePort(Element element) { + String attributes[] = {"name", "type", "basename"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, true); + decreaseXmlIndent(); + return sourceCode; + } + + protected String generateConfiguration(Element element) { + String attributes[] = {"name", "value"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, true); + decreaseXmlIndent(); + return sourceCode; + } + + protected String generateProfiling(Element element) { + String attributes[] = {"name", "value"}; + increaseXmlIndent(); + String sourceCode = generateElement(element, attributes, true); + decreaseXmlIndent(); + return sourceCode; + } + + /** + * + */ + @SuppressWarnings("unchecked") + protected String generateConnection(Element element) { + String attributes[] = {"name"}; + String sourceCode = generateElement(element, attributes, false); + + //add origin and target elements + for (Element childElement : (List)element.getChildren()) { + if (childElement.getName().equalsIgnoreCase("origin")) { + //origin found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns = childElement.getNamespace(); + Element portElement = childElement.getChild("port", ns); + if (portElement != null) { + increaseXmlIndent(); + String[] portAttributes = {"name"}; + sourceCode += generateElement(portElement, + portAttributes, true); + decreaseXmlIndent(); + } + decreaseXmlIndent(); + sourceCode += _indent + + "System.out.println(\" \");\n"; + } + else if (childElement.getName().equalsIgnoreCase(_xt.getTargetTag())) { + //target found + increaseXmlIndent(); + sourceCode += generateElement(childElement, attributes, false); + + Namespace ns = childElement.getNamespace(); + Element portElement = childElement.getChild(_xt.getPortTag(), ns); + if (portElement != null) { + increaseXmlIndent(); + String[] portAttributes = {"name"}; + sourceCode += generateElement(portElement, + portAttributes, true); + decreaseXmlIndent(); + } + decreaseXmlIndent(); + sourceCode += _indent + + "System.out.println(\" \");\n"; + } + } + sourceCode += _indent + "System.out.println(\"\");\n"; + return sourceCode; + } +} diff --git a/dol/src/dol/helper/flattener/SaxDocumentParser.java b/dol/src/dol/helper/flattener/SaxDocumentParser.java new file mode 100644 index 0000000..2394991 --- /dev/null +++ b/dol/src/dol/helper/flattener/SaxDocumentParser.java @@ -0,0 +1,107 @@ +/* $Id: SaxDocumentParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import org.apache.xerces.parsers.SAXParser; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * + */ +class SaxDocumentParser implements ContentHandler{ + + public ErrorHandler bc = new BugCatcher(); + public SAXParser sp = null; + protected boolean foundIterator = false; + + public SaxDocumentParser() { + try { + sp = new SAXParser(); + sp.setFeature("http://xml.org/sax/features/validation",true); + sp.setFeature("http://apache.org/xml/features/validation/schema", true); + sp.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true); + sp.setErrorHandler(bc); + sp.setContentHandler(this); + sp.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", + dol.util.SchemaLocation.getInternalSchemaLocation()); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + + public boolean parseDocument(String pathToFile){ + return parseDocument(new File(pathToFile)); + } + + public boolean parseDocument(File file){ + try{ + InputStream is = new FileInputStream(file); + InputSource iss = new InputSource(is); + sp.parse(iss); + } + catch (Exception e){ + e.printStackTrace(); + } + return true;//foundIterator; + } + + /** + * Action to be done while parsing a start element of an XML + * + * @param elementName Description of the Parameter + * @param attributes Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void startElement(String namespaceURI, String localName, String elementName, Attributes attributes) throws SAXException { + + if (elementName.equals("iterator")) { + //System.out.println(); + //System.out.println("Iterator found in document"); + foundIterator = true; + } + else { + System.out.print("."); + } + } + + + public void startDocument() throws SAXException { + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException { + } + + public void endDocument() throws SAXException { + } + + public void endElement(String namespaceURI, String localName, String elementName) throws SAXException { + } + + public void endPrefixMapping(String prefix) throws SAXException { + } + + public void characters(char buf[], int offset, int len) throws SAXException { + } + + public void skippedEntity(String string){ + } + + public void processingInstruction(String string1, String string2){ + } + + public void ignorableWhitespace(char[] characters,int int1,int int2){ + } + + public void setDocumentLocator(org.xml.sax.Locator dl){ + } + + } + diff --git a/dol/src/dol/helper/flattener/XMLFlattener.java b/dol/src/dol/helper/flattener/XMLFlattener.java new file mode 100644 index 0000000..7aade7c --- /dev/null +++ b/dol/src/dol/helper/flattener/XMLFlattener.java @@ -0,0 +1,71 @@ +/* $Id: XMLFlattener.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.flattener; + +import java.io.File; +import java.io.FileWriter; + +import org.jdom.input.DOMBuilder; +import org.w3c.dom.Document; + +/** + * + */ +public class XMLFlattener { + + public static void main(String[] args) { + + if (args.length != 2) { + System.out.println("Wrong number of arguments."); + System.out.println("Correct call:"); + System.out.println("java XMLFlattener in.xml out.xml"); + System.exit(-1); + } + else { + SaxDocumentParser sdp = new SaxDocumentParser(); + boolean needsToBeFlattened = false; + needsToBeFlattened = sdp.parseDocument(args[0]); + if (needsToBeFlattened) { + // start also with DOM Parser and generate the XML-Generator + DomDocumentParser ddp = new DomDocumentParser(); + Document doc = ddp.parseDocument(args[0]); + + DOMBuilder db = new DOMBuilder(); + org.jdom.Document document = db.build(doc); + + String xmlns = document.getRootElement().getNamespaceURI(); + + String outString = ""; + + if (xmlns.endsWith("PROCESSNETWORK") || + xmlns.endsWith("processnetwork")) { + PNFlattener flattener = new PNFlattener(args[1]); + outString = flattener.removeIterators(document); + } + else if (xmlns.endsWith("ARCHITECTURE") + || xmlns.endsWith("ARCHITECTURE_OLD") + || xmlns.endsWith("architecture")) { + ArchFlattener flattener = new ArchFlattener(args[1]); + outString = flattener.removeIterators(document); + } + else if (xmlns.endsWith("MAPPING") + || xmlns.endsWith("MAPPING_OLD") + || xmlns.endsWith("mapping")) { + MappingFlattener flattener = new MappingFlattener(args[1]); + outString = flattener.removeIterators(document); + } + + + try { + FileWriter fw = new FileWriter(new File(args[1] + ".java")); + fw.write(outString); + fw.flush(); + fw.close(); + } catch (Exception ex) { + } + } + else { + System.out.println("Nothing to be done. Terminating..."); + } + } + } +} diff --git a/dol/src/dol/helper/flattener/package.html b/dol/src/dol/helper/flattener/package.html new file mode 100644 index 0000000..4f62fe4 --- /dev/null +++ b/dol/src/dol/helper/flattener/package.html @@ -0,0 +1,20 @@ + + + + + + +Flattener which unfolds all iterators inside XML files. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/helper/profiler/ChannelProfile.java b/dol/src/dol/helper/profiler/ChannelProfile.java new file mode 100644 index 0000000..4de3cc7 --- /dev/null +++ b/dol/src/dol/helper/profiler/ChannelProfile.java @@ -0,0 +1,221 @@ +/* $Id: ChannelProfile.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.helper.profiler; + +/** + * Functional simulation profile information for a channel. + */ +public class ChannelProfile { + + /** + * Constructor. + * + * @param name name of the channel this profile belongs to + * @param capacity capacity of this channel + */ + public ChannelProfile(String name, int capacity) { + _name = name; + _capacity = capacity; + } + + /** + * Adds a read access. Reduces the fifo fill level accordingly. If the + * amount is larger than the current fifo size, the read is blocking. + * + * @param amount The amount of data in bytes. + */ + public void readAccess(int amount) { + _numOfReads++; + _totalReadData += amount; + + if (amount > _maxReadChunk) + _maxReadChunk = amount; + + if (amount < _minReadChunk) + _minReadChunk = amount; + + if (_fillLevel < amount) { + _numOfBlockingReads++; + _blockedReadSize = amount; + } + else { + _fillLevel -= amount; + } + } + + /** + * Adds a write access. Enlarges the fifo size accordingly. If the + * amount plus the current size of the fifo is larger than the + * capacity, an overflow occurs (blocking write). + * + * @param amount The amount of data in bytes. + */ + public void writeAccess(int amount) { + _numOfWrites++; + if (_fillLevel + amount > _capacity) { + _numOfBlockingWrites++; + } + + _fillLevel += amount; + + if (amount > _maxWriteChunk) + _maxWriteChunk = amount; + + if (amount < _minWriteChunk) + _minWriteChunk = amount; + + if (_fillLevel > _maxFillLevel) + _maxFillLevel = _fillLevel; + + + if ((_fillLevel >= _blockedReadSize) && (_blockedReadSize != 0)) { + _fillLevel -= _blockedReadSize; + _blockedReadSize = 0; + } + } + + /** + * Returns the maximum amount of data stored in the fifo. + * + * @return Maximum fill level in bytes. + */ + public int getMaxFillLevel() { + return _maxFillLevel; + } + + /** + * Returns the size of the largest chunk of data read in a single + * read access. + * + * @return Size of the largest read chunk in bytes. + */ + public int getMaxReadChunk() { + return _maxReadChunk; + } + + /** + * Returns the size of the smallest chunk of data read in a single + * read access. + * + * @return Size of the smallest read chunk in bytes. + */ + public int getMinReadChunk() { + return _minReadChunk; + } + + /** + * Returns the size of the largest chunk of data written in a single + * write access. + * + * @return Size of the largest written chunk in bytes. + */ + public int getMaxWriteChunk() { + return _maxWriteChunk; + } + + /** + * Returns the size of the smallest chunk of data written in a single + * write access. + * + * @return Size of the smallest written chunk in bytes. + */ + public int getMinWriteChunk() { + return _minWriteChunk; + } + + /** + * Returns the number of total read accesses. + * + * @return Number of total read accesses. + */ + public int getNumOfReads() { + return _numOfReads; + } + + /** + * Returns the number of total blocking reads. + * A blocking read occurs whenever there is an attempt to read more + * data from the fifo than the fifo contains. + * + * @return Number of total blocking reads + */ + public int getNumOfBlockingReads() { + return _numOfBlockingReads; + } + + /** + * Returns the total number of write accesses. + * + * @return Number of total write accesses. + */ + public int getNumOfWrites() { + return _numOfWrites; + } + + /** + * Returns the number of total blocking writes. + * A blocking write occurs, whenever a write access tries to write + * more data into the fifo than its capacity allows. + * + * @return Number of total overflows. + */ + public int getNumOfOverflows() { + return _numOfBlockingWrites; + } + + /** + * Returns the percentage of read accesses which were blocking. + * @return Percentage of blocking reads + */ + public int getBlockingReadsPercentage() { + if (_numOfReads == 0) + return 0; + + return (_numOfBlockingReads * 100) / (_numOfReads); + } + /** + * Returns the percentage of write accesses, which led to an overflow. + * @return Percentage of overflows. + */ + public int getBlockingWritesPercentage() { + if (_numOfWrites == 0) + return 0; + + return (_numOfBlockingWrites * 100) / (_numOfWrites); + } + + /** + * Returns the total amount of read data (in bytes). + * @return Amount in bytes. + */ + public long getTotalReadData() { + return _totalReadData; + } + + /** + * Return the name of the channel this profile belongs to. + * + * @return name of the channel this profile belongs to + */ + public String getName() { + return _name; + } + + + protected String _name = ""; + protected int _capacity = 0; + + protected long _totalReadData = 0; + protected int _fillLevel = 0; + protected int _maxFillLevel = 0; + + protected int _numOfReads = 0; + protected int _numOfBlockingReads = 0; + protected int _maxReadChunk = 0; + protected int _minReadChunk = Integer.MAX_VALUE; + protected int _blockedReadSize = 0; + + protected int _numOfWrites = 0; + protected int _numOfBlockingWrites = 0; + protected int _maxWriteChunk = 0; + protected int _minWriteChunk = Integer.MAX_VALUE; +} diff --git a/dol/src/dol/helper/profiler/Constants.java b/dol/src/dol/helper/profiler/Constants.java new file mode 100644 index 0000000..673e1b5 --- /dev/null +++ b/dol/src/dol/helper/profiler/Constants.java @@ -0,0 +1,13 @@ +/* $Id: Constants.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.profiler; + +/** + * Keys for accessing profile properties. + */ +public class Constants { + public final static String processFires = "Process.Fires"; + public final static String portAccesses = "Port.Accesses"; + public final static String portTokenSize = "Port.TokenSize"; + public final static String portInitialAccesses = "Port.InitialAccesses"; + public final static String portInitialTokenSize = "Port.IntialTokensize"; +} diff --git a/dol/src/dol/helper/profiler/PNProfileSummarizer.java b/dol/src/dol/helper/profiler/PNProfileSummarizer.java new file mode 100644 index 0000000..66b1d58 --- /dev/null +++ b/dol/src/dol/helper/profiler/PNProfileSummarizer.java @@ -0,0 +1,105 @@ +/* $Id: PNProfileSummarizer.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.profiler; + +import java.util.HashMap; +import java.util.StringTokenizer; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.parser.xml.pnschema.PNXmlParser; +import dol.visitor.xml.PNXmlVisitor; + +/** + * Class to post-process profiling info in a process network XML file. + */ +public class PNProfileSummarizer { + /** + * Add up all WCED, BCED, and ACED numbers for each process in the + * given process network file. The computed numbers are back-annotated + * to a new file with the suffix _summary. + * + * @param filename filename of annotated process network XML file + */ + public static void sumDemands(String filename) { + HashMap configs = + new HashMap(); + + //load process network specification from XML + PNXmlParser parserPn = new PNXmlParser(); + ProcessNetwork pn = parserPn.doParse(filename); + + for (Process process : pn.getProcessList()) { + //System.out.println(process.getName()); + for (ProfilingConfiguration cfg : + process.getProfilingList()) { + StringTokenizer tokenizer = + new StringTokenizer(cfg.getName(), " "); + //System.out.println(cfg.getName()); + if (tokenizer.countTokens() < 2) { + continue; + } + + String demandId = tokenizer.nextToken(); + String processorName = tokenizer.nextToken(); + String key = process.getName() + " " + demandId + " " + + processorName + " sum"; + if (configs.get(key) == null) { + ProfilingConfiguration config = + new ProfilingConfiguration(demandId + " " + + processorName + " sum"); + config.setValue("0"); + configs.put(key, config); + } + ProfilingConfiguration currentCfg = configs.get(key); + /* + System.out.print(currentCfg.getName() + ": " + + currentCfg.getValue() + " / " + + cfg.getName() + ": " + + cfg.getValue()); + */ + currentCfg.setValue( + Long.toString(Long.parseLong( + currentCfg.getValue()) + + Long.parseLong(cfg.getValue()))); + /* + System.out.println(currentCfg.getName() + ": " + + currentCfg.getValue()); + */ + } + } + + for(String key : configs.keySet()) { + ProfilingConfiguration value = configs.get(key); + String process = key.substring(0, key.indexOf(" ")); + pn.getProcess(process).getProfilingList().add(value); + } + + System.out.println("Write process network XML file"); + StringBuffer buffer = new StringBuffer(); + pn.accept(new PNXmlVisitor(buffer)); + try { + java.io.BufferedWriter writer = + new java.io.BufferedWriter( + new java.io.FileWriter( + filename.replaceAll(".xml", "_summary.xml"))); + writer.write(buffer.toString()); + writer.close(); + } catch (java.io.IOException e) { + System.out.println("Caught an exception while " + + "creating XML file: " + e.getMessage()); + e.printStackTrace(System.out); + } + System.out.println(" -- Process network XML file [Finished]"); + } + + + /** + * Run summarize operations on annotated process network. + * + * @param args filename of annotated process network XML file + */ + public static void main(String args[]) throws Exception { + PNProfileSummarizer.sumDemands(args[0]); + } +} diff --git a/dol/src/dol/helper/profiler/PortProfile.java b/dol/src/dol/helper/profiler/PortProfile.java new file mode 100644 index 0000000..cd040ab --- /dev/null +++ b/dol/src/dol/helper/profiler/PortProfile.java @@ -0,0 +1,95 @@ +/* $Id: PortProfile.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.helper.profiler; + +/** + * Functional simulation profile information for a port. + */ +public class PortProfile { + + /** + * Constructor. + * + * @param name name of the port this profile belongs to + */ + public PortProfile(String name, ProcessProfile processProfile) { + _name = name; + _processProfile = processProfile; + _accesses = new Range(); + _tokenSize = new Range(); + _currentTokenSize = new Range(); + } + + /** + * Return the name of the channel this profile belongs to. + * + * @return name of the channel this profile belongs to + */ + public String getName() { + return _name; + } + + /** + * Add a read or write access to this port. + * + * @param tokenSize number of bytes communicated in this access + */ + public void addAccess(int tokenSize) { + _currentAccesses++; + _currentTokenSize.merge(tokenSize); + } + + /** + * Add an initial read or write access to this port (access happened + * during init() phase). + * + * @param tokenSize number of bytes communicated in this access + */ + public void addInitialAccess(int tokenSize) { + _initialAccesses++; + if (_initialTokenSize == null) { + _initialTokenSize = new Range(tokenSize); + } else { + _initialTokenSize.merge(tokenSize); + } + } + + /** + * + */ + public void update() { + _accesses.merge(_currentAccesses); + _tokenSize.merge(_currentTokenSize); + + _currentAccesses = 0; + _currentTokenSize.reset(); + } + + public int getInitialAccesses() { + return _initialAccesses; + } + + public Range getInitialTokenSize() { + if (_initialTokenSize == null) { + return new Range(0); + } else { + return _initialTokenSize; + } + } + + public Range getAccesses() { + return _accesses; + } + + public Range getTokenSize() { + return _tokenSize; + } + + String _name; + Range _accesses; + Range _tokenSize; + int _currentAccesses; + Range _currentTokenSize; + int _initialAccesses; + Range _initialTokenSize = null; + ProcessProfile _processProfile; +} diff --git a/dol/src/dol/helper/profiler/ProcessProfile.java b/dol/src/dol/helper/profiler/ProcessProfile.java new file mode 100644 index 0000000..f8e51a8 --- /dev/null +++ b/dol/src/dol/helper/profiler/ProcessProfile.java @@ -0,0 +1,97 @@ +/* $Id: ProcessProfile.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.helper.profiler; + +import java.util.HashMap; +import java.util.Iterator; + +/** + * Functional simulation profile information for a process. + */ +public class ProcessProfile { + + /** + * Constructor. + * + * @param name name of the process this profile belongs to + */ + public ProcessProfile(String name) { + _name = name; + _portProfiles = new HashMap(); + } + + /** + * Indicate that the process has entered fire(). + */ + public void start() { + _started = true; + _numOfFires++; + } + + /** + * Indicate that the process has leaved fire(). + */ + public void stop() { + if (!_started) { + return; + } + + _started = false; + + Iterator iterator = _portProfiles.keySet().iterator(); + + while (iterator.hasNext()) { + PortProfile profile = _portProfiles.get(iterator.next()); + profile.update(); + } + } + + /** + * Indicate a read or write access to a port. + * + * @param port the accessed port + * @param amount number of bytes communicated + */ + public void portAccess(String port, int amount) { + if (_portProfiles.get(port) == null) { + PortProfile profile = new PortProfile(port, this); + _portProfiles.put(port, profile); + } + if (_started) { + _portProfiles.get(port).addAccess(amount); + } else { + _portProfiles.get(port).addInitialAccess(amount); + } + } + + /** + * Return the number of firings. + * + * @return the number of fire() calls of a process. + **/ + public int getNumOfFires() { + return _numOfFires; + } + + /** + * Return the name of the process this profile belongs to. + * + * @return name of the process this profile belongs to + */ + public String getName() { + return _name; + } + + /** + * Return the profiles of all ports. + * + * @return profile of all ports + */ + public HashMap getPortProfiles() { + return _portProfiles; + } + + protected String _name; + boolean _started = false; + protected int _numOfFires = 0; + HashMap _portProfiles; +} diff --git a/dol/src/dol/helper/profiler/ProfileParser.java b/dol/src/dol/helper/profiler/ProfileParser.java new file mode 100644 index 0000000..5ad1304 --- /dev/null +++ b/dol/src/dol/helper/profiler/ProfileParser.java @@ -0,0 +1,273 @@ +/* $Id: ProfileParser.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.helper.profiler; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Class for parsing a profile and collecting parameter statistics. + */ +public class ProfileParser { + + /** + * Constructor. + **/ + public ProfileParser(String filename) { + try { + _in = new BufferedReader(new FileReader(filename)); + } catch (IOException e) { + System.err.println(e.getLocalizedMessage()); + } + + _processProfiles = new HashMap(); + _channelProfiles = new HashMap(); + _inPortToChannelMapping = new HashMap(); + _outPortToChannelMapping = new HashMap(); + _processCommOrder = new HashMap>(); + } + + /** + * Parse the profile file and generate the profiles for processes and + * channels. After calling this function, use + * {@link #getProcessProfiles()} and {@link #getChannelProfiles()} to + * obtain the result of parsing. + * + * @see #getChannelProfiles() + * @see #getProcessProfiles() + */ + public void parseProfile() { + String nextWord; + String line = null; + + while (true) { + try { + //PERFORMANCE: do not read line-by-line but read in larger + //chunks from the file. has much more influence on the + //performance than the data structures in this class. + line = _in.readLine(); + if (line == null) { + _in.close(); + + Iterator iterator = _processProfiles.keySet().iterator(); + while (iterator.hasNext()) { + _processProfiles.get(iterator.next()).stop(); + } + return; + } + } catch (IOException e) { + System.err.println(e.getLocalizedMessage()); + return; + } + + StringTokenizer tokenizer = new StringTokenizer(line); + nextWord = tokenizer.nextToken(); + + if (nextWord.equals("c")) { + //'c' stands for a channel connection line. example: + //c filterchannel 8 o filter 0x23c738 i filter 0x23c6e8 + String channelName = tokenizer.nextToken(); + int capacity = Integer.parseInt( + tokenizer.nextToken()); + String portAType = tokenizer.nextToken(); + String processAName = tokenizer.nextToken(); + String portAName = tokenizer.nextToken(); + String portBType = tokenizer.nextToken(); + String processBName = tokenizer.nextToken(); + String portBName = tokenizer.nextToken(); + addChannelProfile(channelName, capacity, + portAType, processAName, portAName, + portBType, processBName, portBName); + } else { + //current line is an event. examples: + //examples: + //78 filter started. + //79 filter r 0x23c6c0 8 + //80 filter w 0x23c738 8 + //81 filter stopped. + + int i = Integer.parseInt(nextWord); + if (i != _lineCounter) { + System.err.println("Input file corrupt: line number " + + "expected."); + return; + } + + //get process name + String processName = tokenizer.nextToken(); + if (_processProfiles.get(processName) == null) { + ProcessProfile processProfile = new ProcessProfile( + processName); + _processProfiles.put(processName, processProfile); + } + if(_processCommOrder.get(processName) == null) { + _processCommOrder.put(processName, new Vector()); + } + + nextWord = tokenizer.nextToken(); + if(nextWord.equals("started.")) { + _processProfiles.get(processName).start(); + } + else if(nextWord.equals("stopped.")) { + _processProfiles.get(processName).stop(); + } + else if (nextWord.equals("r") || nextWord.equals("w")) { + String accessType = nextWord; + String portName = tokenizer.nextToken(); + int amount = Integer.parseInt( + tokenizer.nextToken()); + try { + _processProfiles.get(processName). + portAccess(portName, amount); + if (accessType.equals("r")) { + String channelName = + _inPortToChannelMapping.get(portName); + _channelProfiles.get(channelName). + readAccess(amount); + _processCommOrder.get(processName).add(channelName); + + } else { + String channelName = + _outPortToChannelMapping.get(portName); + _channelProfiles.get(channelName). + writeAccess(amount); + _processCommOrder.get(processName).add(channelName); + } + } catch (NullPointerException e) { + System.err.println("Input file corrupt: cannot " + + "find channel associated to port " + + portName + "(line " + + _lineCounter + ")."); + e.printStackTrace(); + return; + } + } else { + System.err.println("Input file corrupt: unknown " + + "event type (line " + _lineCounter + ")."); + return; + } + _lineCounter++; + } + } + } + + /** + * Return the profiles of all processes. + * + * @return profile of all processes + */ + public HashMap getProcessProfiles() { + return _processProfiles; + } + + /** + * Return the profiles of all channels. + * + * @return profile of all channels + */ + public HashMap getChannelProfiles() { + return _channelProfiles; + } + + /** + * Return the order in which processes write to channels. + * + * @return profile of all processes + */ + public HashMap> getProcessCommOrder() { + return _processCommOrder; + } + + /** + * Return the channel to which the port with the specified name is + * connected. + * + * @param port port name + * @return name of connected channel + */ + public String getChannel(String port) { + if (_inPortToChannelMapping.get(port) != null) { + return _inPortToChannelMapping.get(port); + } else if (_outPortToChannelMapping.get(port) != null) { + return _outPortToChannelMapping.get(port); + } + return null; + } + + /** + * Return the type of the channel with the specified name. + * + * @param port port name + * @return type of port + */ + public String getPortType(String port) { + if (_inPortToChannelMapping.get(port) != null) { + return "INPUT"; + } else if (_outPortToChannelMapping.get(port) != null) { + return "OUTPUT"; + } + return null; + } + + /** + * Add a channel profile to the HashMap of channel profiles. + * + * @param channelName name of the channel + * @param capacity capacity of the channel + * @param portAType type of first port (either "o" or "i") + * @param processAName name of process connected to first port + * @param portAName name of first port + * @param portBType type of second port (either "o" or "i") + * @param processBName name of process connected to second port + * @param portBName name of second port + */ + protected void addChannelProfile(String channelName, int capacity, + String portAType, String processAName, String portAName, + String portBType, String processBName, String portBName) { + + ChannelProfile channelProfile = + new ChannelProfile(channelName, capacity); + _channelProfiles.put(channelName, channelProfile); + + if (portAType.equals("o")) { + //first output port, then input port + _outPortToChannelMapping.put(portAName, channelName); + if (!(portBType.equals("i"))) { + System.err.println("Input file corrupt: each " + + "channel needs one input- and one " + + "output port."); + return; + } + _inPortToChannelMapping.put(portBName, channelName); + } else if (portAType.equals("i")) { + //first input port, then output port + _inPortToChannelMapping.put(portAName, channelName); + if (!(portBType.equals("o"))) { + System.err.println("Input file corrupt: each " + + "channel needs one input- and one " + + "output port."); + return; + } + _outPortToChannelMapping.put(portBName, channelName); + } else { + System.err.println("Input file corrupt: bad channel " + + "specification:"); + System.err.println(channelName + " " + capacity + " " + + portAType + " " + processAName + " " + portAName + + " " + + portBType + " " + processBName + " " + portBName); + } + } + + private BufferedReader _in = null; + protected int _lineCounter = 0; + HashMap _processProfiles; + HashMap _channelProfiles; + HashMap _inPortToChannelMapping; + HashMap _outPortToChannelMapping; + HashMap> _processCommOrder; +} diff --git a/dol/src/dol/helper/profiler/Profiler.java b/dol/src/dol/helper/profiler/Profiler.java new file mode 100644 index 0000000..e24d456 --- /dev/null +++ b/dol/src/dol/helper/profiler/Profiler.java @@ -0,0 +1,212 @@ +/* $Id: Profiler.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.helper.profiler; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Vector; + +import dol.datamodel.XmlTag; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; + + +/** + * DOL profiler main class. Reads in profile data and generates the + * profiling information. + */ +public class Profiler { + + /** + * Constructor + */ + public Profiler() { + } + + /** + * @param name Name of the trace file + * @param pn The process network need to be annotated + */ + public void profilePN(String name, ProcessNetwork pn) { + ProfileParser profileParser = new ProfileParser(name); + profileParser.parseProfile(); + + HashMap channelProfiles = + profileParser.getChannelProfiles(); + Iterator iterator = channelProfiles.keySet().iterator(); + + while (iterator.hasNext()) { + ChannelProfile profile = channelProfiles.get(iterator.next()); + Channel channel = pn.getChannel(profile.getName()); + ProfilingConfiguration c = channel.getProfilingCfg( + _xt.getProfilingTotalReadData()); + if (c == null) { + //no profiling present, add new one + c = new ProfilingConfiguration( + _xt.getProfilingTotalReadData()); + c.setValue(Long.toString(profile.getTotalReadData())); + channel.getProfilingList().add(c); + } else { + // profiling already present, extend existing one + Long curr = Long.decode(c.getValue()); + curr += profile.getTotalReadData(); + c.setValue(Long.toString(curr)); + } + + c = channel.getProfilingCfg(_xt.getProfilingNumOfReads()); + if (c == null) { + c = new ProfilingConfiguration( + _xt.getProfilingNumOfReads()); + c.setValue(Integer.toString(profile.getNumOfReads())); + channel.getProfilingList().add(c); + } else { + Long curr = Long.decode(c.getValue()); + curr += profile.getNumOfReads(); + c.setValue(Long.toString(curr)); + } + + c = channel.getProfilingCfg(_xt.getProfilingNumOfWrites()); + if (c == null) { + c = new ProfilingConfiguration( + _xt.getProfilingNumOfWrites()); + c.setValue(Integer.toString(profile.getNumOfWrites())); + channel.getProfilingList().add(c); + } else { + Long curr = Long.decode(c.getValue()); + curr += profile.getNumOfWrites(); + c.setValue( Long.toString(curr) ); + } + } + + HashMap processProfiles = + profileParser.getProcessProfiles(); + iterator = processProfiles.keySet().iterator(); + + while (iterator.hasNext()) { + ProcessProfile profile = processProfiles.get(iterator.next()); + Process process = pn.getProcess(profile.getName()); + ProfilingConfiguration c = process.getProfilingCfg( + _xt.getProfilingNumOfFires()); + + if (c == null) { + c = new ProfilingConfiguration( + _xt.getProfilingNumOfFires()); + c.setValue(Integer.toString(profile.getNumOfFires())); + process.getProfilingList().add(c); + } else { + int curr = Integer.valueOf(c.getValue()); + curr += profile.getNumOfFires(); + c.setValue(Integer.toString(curr)); + } + + HashMap portProfiles = + profile.getPortProfiles(); + Iterator iteratorB = portProfiles.keySet().iterator(); + + while (iteratorB.hasNext()) { + PortProfile portProfile = + portProfiles.get(iteratorB.next()); + String portName = getPNPortName(portProfile.getName(), profileParser, pn); + c = new ProfilingConfiguration(portName + ".accesses"); + c.setValue(portProfile.getAccesses().toString()); + process.getProfilingList().add(c); + c = new ProfilingConfiguration(portName + ".tokensize"); + c.setValue(portProfile.getTokenSize().toString()); + process.getProfilingList().add(c); + c = new ProfilingConfiguration(portName + ".initialAccesses"); + c.setValue(Integer.toString(portProfile.getInitialAccesses())); + process.getProfilingList().add(c); + c = new ProfilingConfiguration(portName + ".initialtokensize"); + c.setValue(portProfile.getInitialTokenSize().toString()); + process.getProfilingList().add(c); + } + } + } + + /** + * + */ + protected String getPNPortName(String profilePortName, + ProfileParser parser, ProcessNetwork pn) { + Vector ports = pn.getChannel(parser. + getChannel(profilePortName)).getPortList(); + for (int i = 0; i < ports.size(); i++) { + if (parser.getPortType(profilePortName).equals("INPUT") && + ports.elementAt(i).isOutPort()) { + return ports.elementAt(i).getPeerPort().getName(); + } else if (parser.getPortType(profilePortName).equals("OUTPUT") && + ports.elementAt(i).isInPort()) { + return ports.elementAt(i).getPeerPort().getName(); + } + } + return null; + } + + /** + * Main function + * + * @param args command line arguments. args[0] is the filename of + * the profile file. args[1] is the filename of the process network + * XML file. + */ + public static void main(String[] args) { + //check command line parameters + if (args.length != 2) { + System.out.println("Usage: Profiler "); + System.out.println(); + return; + } + + ProfileParser profileParser = new ProfileParser(args[0]); + + //parse input file + profileParser.parseProfile(); + + //print the results + System.out.println("----- Channel Analyzer Report -----"); + System.out.println(" " + + " " + + " "); + + HashMap channelProfiles = + profileParser.getChannelProfiles(); + Iterator iterator = channelProfiles.keySet().iterator(); + + while (iterator.hasNext()) { + ChannelProfile profile = channelProfiles.get(iterator.next()); + System.out.println( "<" + profile.getName() + + "> " + + profile.getMaxFillLevel() + " " + + profile.getTotalReadData() + " " + + profile.getNumOfReads() + " " + + profile.getNumOfWrites()); + } + System.out.println(); + + System.out.println("----- Processnetwork Report -----"); + System.out.println(" "); + + HashMap processProfiles = + profileParser.getProcessProfiles(); + iterator = processProfiles.keySet().iterator(); + while (iterator.hasNext()) { + ProcessProfile profile = processProfiles.get(iterator.next()); + System.out.println( "<" + profile.getName() + + "> " + profile.getNumOfFires()); + } + System.out.println(); + + /* + //another test + PNXmlParser parserPN = new PNXmlParser(); + ProcessNetwork pn = parserPN.doParse(args[1]); + Profiler p = new Profiler(); + p.profilePN(args[0], pn); + pn.accept(new PNXmlVisitor("processnetwork.xml")); + */ + } + + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/helper/profiler/Range.java b/dol/src/dol/helper/profiler/Range.java new file mode 100644 index 0000000..7c7721f --- /dev/null +++ b/dol/src/dol/helper/profiler/Range.java @@ -0,0 +1,176 @@ +/* $Id: Range.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.profiler; + +import java.util.Collections; +import java.util.Vector; + +/** + * Tuple of numbers representing an integer range. Except for an + * uninitialized range when using the default constructor, the + * range values are sorted, such that {@link #getLower()} returns + * the lower bound and {@link #getUpper()} the upper bound of the + * range. + */ +public class Range { + + protected int _lower = Integer.MAX_VALUE; + protected int _upper = Integer.MIN_VALUE; + protected int _initialLower = Integer.MAX_VALUE; + protected int _initialUpper = Integer.MIN_VALUE; + public static final String DELIMITER = "-"; + + /** + * Default constructor. When merging this range with another + * range, always the resulting range will have the values of + * the other range. + */ + public Range() { + } + + /** + * Construct a range with equal upper and lower bound. + * + * @param value upper and lower bound of range + */ + public Range(int value) { + _lower = value; + _upper = value; + _initialLower = _lower; + _initialUpper = _upper; + } + + /** + * Construct a range with the two given bounds. + * + * @param valueA upper or lower bound + * @param valueB upper or lower bound + */ + public Range(int valueA, int valueB) { + _lower = Math.min(valueA, valueB); + _upper = Math.max(valueA, valueB); + _initialLower = _lower; + _initialUpper = _upper; + } + + /** + * Construct a range with upper and lower bounds that are the minimum + * and maximum of the given vector. + * + * @param values vector whose minimum and maximum value are used as + * bounds for the range + */ + public Range(Vector values) { + if (values == null || values.size() == 0) { + return; + } + + _lower = Collections.min(values); + _upper = Collections.max(values); + } + + /** + * Return lower bound of this range. + * + * @return lower bound + */ + protected int getLower() { + return _lower; + } + + /** + * Return upper bound of this range. + * + * @return upper bound + */ + protected int getUpper() { + return _upper; + } + + /** + * Combine range with another range. The lower bound of the range is + * the minimum of the lower bounds of this range and the other range. + * The upper bound of the range is the maximum of the upper bounds of + * this range and the other range. + * + * @param range to combine with this range + */ + public void merge(Range range) { + if (range == null) + return; + + _lower = Math.min(_lower, range.getLower()); + _upper = Math.max(_upper, range.getUpper()); + } + + /** + * Combine range with a value. The lower bound of the range is + * the minimum of the lower bound of this range and the value. + * The upper bound of the range is the maximum of the upper bound of + * this range and the value. + * + * @param value integer value to combine with this range + */ + public void merge(int value) { + _lower = Math.min(_lower, value); + _upper = Math.max(_upper, value); + } + + /** + * Create a string representation of the range. + * + * @return string representation of the range + */ + public String toString() { + if (_upper == _lower) + return Integer.toString(_upper); + return _lower + DELIMITER + _upper; + } + + /** + * Create a range based on a string representation of a range. + * + * @param string representation of a range + * @return range + * @throws IllegalArgumentException + */ + public static Range valueOf(String string) throws IllegalArgumentException { + java.util.regex.Pattern pattern = + java.util.regex.Pattern.compile( + "([-]?[0-9]+)[" + DELIMITER + "]?([-]?[0-9]+)?"); + java.util.regex.Matcher m = pattern.matcher(string); + if (!m.matches()) { + throw new IllegalArgumentException("String does not " + + "represent a range."); + } + return new Range(Integer.valueOf(m.group(1)), + Integer.valueOf(m.group(2))); + } + + /** + * Reset this range to the values it had when it has been constructed. + */ + public void reset() { + _lower = _initialLower; + _upper = _initialUpper; + } + + /** + * Test cases for range class. + */ + public static void main(String args[]) throws Exception { + Range a = new Range(1); + System.out.println(a); + + Range b = new Range(1, 3); + System.out.println(b); + + Range c = new Range(4, 3); + System.out.println(c); + + b.merge(c); + System.out.println(b); + + Range d = Range.valueOf("-123" + Range.DELIMITER + "-456"); + System.out.println(d); + } +} \ No newline at end of file diff --git a/dol/src/dol/helper/profiler/VSPLogFileProfiler.java b/dol/src/dol/helper/profiler/VSPLogFileProfiler.java new file mode 100644 index 0000000..dc3b827 --- /dev/null +++ b/dol/src/dol/helper/profiler/VSPLogFileProfiler.java @@ -0,0 +1,73 @@ +/* $Id: VSPLogFileProfiler.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.profiler; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.StringTokenizer; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; + +/** + * Class for parsing a file with profiling info and annotate it to a + * process network. + */ +public class VSPLogFileProfiler { + + /** + * Parse the given file and annotate the data to the given process + * network. + * + * @param filename file with profiling info + * @param pn processnetwork to annotate + */ + public static void annotateProcessNetwork(String filename, + ProcessNetwork pn) { + try { + BufferedReader reader = new BufferedReader( + new FileReader(filename)); + String line; + while ((line = reader.readLine()) != null) { + StringTokenizer tokenizer = + new StringTokenizer(line, " "); + if (tokenizer.countTokens() == 2) { + continue; + } + else if (tokenizer.countTokens() <= 2) { + System.out.println("Warning: Each line in the log " + + "file should have the following form:" + + System.getProperty("line.separator") + + "processname config_name config_value" + + System.getProperty("line.separator") + + "Ignoring the non-conforming line:" + + System.getProperty("line.separator") + + line); + continue; + } + String processname = tokenizer.nextToken(); + Process process = pn.getProcess(processname); + if (process == null) { + System.out.println("Warning: Could not find process " + + processname + " in processnetwork " + + pn.getName() + ". Ignore configuration " + + "statement in file " + filename + "."); + continue; + } + ProfilingConfiguration config = + new ProfilingConfiguration(tokenizer.nextToken()); + String value = ""; + while (tokenizer.hasMoreTokens()) { + value += tokenizer.nextToken() + " "; + } + config.setValue(value.trim()); + config.setParentResource(process); + process.getProfilingList().add(config); + } + } + catch (IOException e) { + System.out.println(e); + } + } +} diff --git a/dol/src/dol/helper/profiler/WorkloadAnnotator.java b/dol/src/dol/helper/profiler/WorkloadAnnotator.java new file mode 100644 index 0000000..fb6b19d --- /dev/null +++ b/dol/src/dol/helper/profiler/WorkloadAnnotator.java @@ -0,0 +1,102 @@ +/* $Id: WorkloadAnnotator.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.profiler; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.parser.xml.pnschema.PNXmlParser; +import dol.visitor.xml.PNXmlVisitor; + +public class WorkloadAnnotator { + + /** + * + * @param args + */ + public static void main(String[] args) { + String logFile = "workload.txt"; + String pnFileIn = "processnetwork.xml"; + String pnFileOut = "processnetwork.xml"; + + try { + if (args.length > 2) { + pnFileIn = args[0]; + logFile = args[1]; + pnFileOut = args[1]; + } else if (args.length == 2) { + pnFileIn = args[0]; + logFile = args[1]; + } else if (args.length == 1) { + pnFileIn = args[0]; + } + + PNXmlParser parserPN = new PNXmlParser(); + ProcessNetwork pn = parserPN.doParse(pnFileIn); + + WorkloadAnnotator.annotateProcessNetwork(logFile, pn); + StringBuffer b = new StringBuffer(); + pn.accept(new PNXmlVisitor(b)); + FileWriter out = new FileWriter(pnFileOut); + out.write(b.toString()); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * @param workloadFileName + * @param pn + * @throws IOException + */ + public static void annotateProcessNetwork(String workloadFileName, + ProcessNetwork pn) throws IOException { + for (Process p : pn.getProcessList()) { + Vector v = p.getProfilingList(); + if (v == null) { + p.setProfilingList(new Vector()); + } + } + + BufferedReader in = new BufferedReader(new FileReader(workloadFileName)); + String line, process, name, value; + while ((line = in.readLine()) != null) { + if (line.startsWith("wced_")) { + process = line.substring("wced_".length()).trim(); + name = "WCET"; + + } else if (line.startsWith("bced_")) { + process = line.substring("bced_".length()).trim(); + name = "BCET"; + + } else if (line.startsWith("workload_upper_")) { + process = line.substring("workload_upper_".length()). + trim(); + name = "WORKLOAD_UPPER"; + + } else if (line.startsWith("workload_lower_")) { + process = line.substring("workload_lower_".length()). + trim(); + name = "WORKLOAD_LOWER"; + + } else { + continue; + } + process = process.substring(0, process.length() - 1).trim(); + + in.readLine(); + value = in.readLine().trim(); + + ProfilingConfiguration pc = new ProfilingConfiguration(name); + pc.setValue(value); + pn.getProcess(process).getProfilingList().add(pc); + } + } +} diff --git a/dol/src/dol/helper/profiler/package.html b/dol/src/dol/helper/profiler/package.html new file mode 100644 index 0000000..77be30d --- /dev/null +++ b/dol/src/dol/helper/profiler/package.html @@ -0,0 +1,20 @@ + + + + + + +Profiler to extract parameters from a profiling file. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/helper/validator/XMLValidator.java b/dol/src/dol/helper/validator/XMLValidator.java new file mode 100644 index 0000000..b65b9a8 --- /dev/null +++ b/dol/src/dol/helper/validator/XMLValidator.java @@ -0,0 +1,77 @@ +/* $Id: XMLValidator.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.helper.validator; + +import java.io.IOException; + +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + * Class to check well-formedness and validity of XML documents. + */ +public class XMLValidator { + + /** + * Check the well-formedness and validity of an XML document. + * The file is checked against the schema which is referenced from + * within the file. When the document is an XML schema, it is checked + * against http://www.w3.org/2001/XMLSchema.xsd. + * + * @param filename filename of the file to be checked + */ + public static boolean isValid(String filename) { + SAXBuilder builder = new SAXBuilder(true); + builder.setFeature("http://xml.org/sax/features/validation", + true); + builder.setFeature("http://apache.org/xml/features/validation/" + + "schema", true); + builder.setFeature("http://apache.org/xml/features/validation/" + + "schema-full-checking", true); + builder.setProperty("http://apache.org/xml/properties/schema/" + + "external-schemaLocation", + "http://www.w3.org/2001/XMLSchema " + + "http://www.w3.org/2001/XMLSchema.xsd " + + dol.util.SchemaLocation.getExternalSchemaLocation()); + try { + builder.build(filename); + } + catch (JDOMException e) { + System.out.println("Found an error in " + filename + "."); + System.out.println(e.getMessage()); + System.out.println(""); + return false; + } + catch (IOException e) { + System.out.println("Found an error in " + filename + "."); + System.out.println(e.getMessage()); + System.out.println(""); + return false; + } + + return true; + } + + /** + * Main function. We might extend the validator to validate different + * kind of XMLs based on different schemas. + * + * @param args The command line argument. + */ + public static void main(String[] args) { + + /* Check command line parameters */ + /* maybe add options to check both external/internal schema */ + if (args.length != 1) { + System.out.println("Usage: XMLValidator "); + System.out.println(); + return; + } + + if (XMLValidator.isValid(args[0])){ + System.out.println(args[0] + " is valid."); + } else { + System.exit(1); + } + } +} diff --git a/dol/src/dol/helper/validator/package.html b/dol/src/dol/helper/validator/package.html new file mode 100644 index 0000000..b9e7084 --- /dev/null +++ b/dol/src/dol/helper/validator/package.html @@ -0,0 +1,20 @@ + + + + + + +Utilities to check well-formedness and validity of XML documents. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/main/Main.java b/dol/src/dol/main/Main.java new file mode 100644 index 0000000..6d0cd95 --- /dev/null +++ b/dol/src/dol/main/Main.java @@ -0,0 +1,263 @@ +/* $Id: Main.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.main; + +import java.io.FileOutputStream; +import java.io.OutputStream; + +import dol.check.SanityCheck; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.ProcessNetwork; +import dol.helper.profiler.Profiler; +import dol.helper.profiler.VSPLogFileProfiler; +import dol.helper.profiler.WorkloadAnnotator; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; +import dol.parser.xml.pnschema.PNXmlParser; +import dol.util.CodePrintStream; +import dol.visitor.PipeAndFilter.PipeAndFilterVisitor; +import dol.visitor.cell.CellVisitor; +import dol.visitor.dot.ArchDotVisitor; +import dol.visitor.dot.MapDotVisitor; +import dol.visitor.dot.PNDotVisitor; +import dol.visitor.hds.HdsVisitor; +import dol.visitor.hdsd.HdsdVisitor; +import dol.visitor.protothread.ProtothreadVisitor; +import dol.visitor.rtems.RtemsVisitor; +import dol.visitor.systemC.PNSystemCVisitor; +import dol.visitor.xml.PNXmlVisitor; +import dol.visitor.yapi.YapiVisitor; + +/** + * Distributed Operating Layer (DOL) + * + * This class is the main controlling part of DOL. In this class, the + * command line options are processed, the input and output files are set, + * and the complete compilation cycle is done. Also, this class is the + * final responder to exceptions occurring within the DOL. + */ +public class Main { + + /** + * The main method of this class + * + * @param args The arguments to provide to DOL. + */ + public static void main(String[] args) { + + _ui = UserInterface.getInstance(); + + try { + new Options(args); + } catch (NumberFormatException e) { + System.out.println("Error in Command line options: " + + " the numerial format for an argument is" + + " incorrect. Message: " + + e.getMessage()); + System.exit(-1); + } catch (IllegalArgumentException e) { + System.out.println("Error in Command line option: " + + e.getMessage()); + System.exit(-1); + } catch (Exception e) { + System.out.println(e.getMessage()); + System.exit(-1); + } + + try { + //loader + + //load process network specification from XML + if (_ui.getNetworkFileName() != null) { + PNXmlParser parserPN = new PNXmlParser(); + _pn = parserPN.doParse(_ui.getNetworkFileName()); + } + + //load architecture specification from XML + if (_ui.getPlatformFileName() != null) { + ArchiXmlParser parserArch = new ArchiXmlParser(); + _architecture = parserArch.doParse( + _ui.getPlatformFileName()); + + //not useful (archiPathFinderVisitor not functional) + //_architecture.setArchiConnections(); + //_architecture.accept(new dol.visitor.pathFinder. + // archiPathFinderVisitor()); + //_architecture.accept(new dol.visitor.pathFinder. + // archiPathPrinterVisitor()); + } + + //load mapping specification from XML + if ((_pn != null) && (_architecture != null) + && _ui.getMappingFileName() != null) { + MapXmlParser parserMap = new MapXmlParser(_pn, _architecture); + _mapping = parserMap.doParse(_ui.getMappingFileName()); + } + + //sanity check + if (_ui.getCheckFlag()){ + System.out.println("Consistency check:"); + //check process network + if (_pn != null) + SanityCheck.getInstance().checkPN(_pn); + + //check architecture + if (_architecture != null) + SanityCheck.getInstance().checkArch(_architecture); + + //check mapping + if (_mapping != null) + SanityCheck.getInstance().checkMap(_mapping); + + System.out.println(" -- Consistency check [Finished]"); + System.out.println(); + } + + // analyze profiling data and fill into pn + if (_ui.getProfilingFlag() && (_pn != null)) { + System.out.println(" -- ProcessNetwork profiling"); + Profiler p = new Profiler(); + p.profilePN(_ui.getTraceName(), _pn); + System.out.println(" -- Profiling [Finished]"); + System.out.println(); + } + + if (_ui.getVspLogFlag() && (_pn != null)) { + System.out.println("VSP log file back-annotation:"); + VSPLogFileProfiler.annotateProcessNetwork( + _ui.getVspLogFileName(), _pn); + System.out.println(" -- Back-annotation [Finished]"); + System.out.println(); + } + + if (_ui.getWorkloadFlag() && (_pn != null)) { + System.out.println("Workload file back-annotation:"); + WorkloadAnnotator.annotateProcessNetwork( + _ui.getWorkloadFileName(), _pn); + System.out.println(" -- Back-annotation [Finished]"); + System.out.println(); + } + + //Generator + CodePrintStream printStream; + + if (_ui.getDottyFlag()) { + OutputStream file = new FileOutputStream(_ui.getDottyFileName()); + printStream = new CodePrintStream(file); + if (_mapping != null) + { + System.out.println("Generating Mapping in Dotty format:"); + _mapping.accept(new MapDotVisitor(printStream)); + } + else if (_pn != null) + { + System.out.println("Generating ProcessNetwork in Dotty format:"); + _pn.accept(new PNDotVisitor(printStream)); + } + else if (_architecture!=null) { + System.out.println("Generating Architecture in Dotty format:"); + _architecture.registerRWPath2Resource(); + _architecture.accept(new ArchDotVisitor(printStream)); + } + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getXmlGenFlag() && (_pn != null)) { + System.out.println("Generating ProcessNetwork in XML format:"); + StringBuffer buffer = new StringBuffer(); + _pn.accept(new PNXmlVisitor(buffer)); + try { + java.io.BufferedWriter writer = + new java.io.BufferedWriter( + new java.io.FileWriter(_ui.getOutputFileName())); + writer.write(buffer.toString()); + writer.close(); + } catch (java.io.IOException e) { + System.out.println(" -- DOL caught an exception while " + + "creating XML file: " + e.getMessage()); + e.printStackTrace(System.out); + } + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getSystemCFlag() && (_pn != null)) { + System.out.println("Generating SystemC package:"); + _pn.accept(new PNSystemCVisitor(_ui.getCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getPipeAndFilterFlag() && (_pn != null)) { + System.out.println("Generating PipeAndFilter package:"); + _pn.accept(new PipeAndFilterVisitor( + _ui.getPipeAndFilterCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getProtothreadFlag() && (_pn != null)) { + System.out.println("Generating protothread package:"); + _pn.accept(new ProtothreadVisitor( + _ui.getProtothreadCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getRtemsFlag() && (_pn != null)) { + String bsp = _ui.getRtemsBSP(); + System.out.println("Generating RTEMS-" + bsp + " package:"); + _pn.accept(new RtemsVisitor( + _ui.getRtemsCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getCbeFlag() && (_pn != null)) { + System.out.println("Generating Cell-package:"); + _pn.accept(new CellVisitor( + _ui.getCbeCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getYapiFlag() && (_pn != null)) { + System.out.println("Generating YAPI-package:"); + _pn.accept(new YapiVisitor( + _ui.getYapiCodeDirectoryName())); + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + if (_ui.getHdsFlag() && (_pn != null)) { + System.out.println("Generating HdS package:"); + if ((_pn!=null) && (_architecture!=null) && (_mapping!=null)) { + System.out.println("Generating distributed HdS package:"); + // Hds with networking supportSAXException + _mapping.accept(new HdsdVisitor(_ui.getHdsCodeDirectoryName())); + + } else { + // Hds without networking support + _pn.accept(new HdsVisitor(_ui.getHdsCodeDirectoryName())); + } + System.out.println(" -- Generation [Finished]"); + System.out.println(); + } + + } + catch (NumberFormatException e) { + System.out.println(" ERROR Occured in DOL: " + e.getMessage()); + e.printStackTrace(System.out); + } + catch (Exception e) { + System.out.println(" DOL caught an exception: " + e.getMessage()); + e.printStackTrace(System.out); + } + } + + protected static ProcessNetwork _pn = null; + protected static Architecture _architecture = null; + protected static Mapping _mapping = null; + protected static UserInterface _ui = null; +} diff --git a/dol/src/dol/main/Options.java b/dol/src/dol/main/Options.java new file mode 100644 index 0000000..b284d90 --- /dev/null +++ b/dol/src/dol/main/Options.java @@ -0,0 +1,207 @@ +/* $Id: Options.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.main; + +/** + * This class handles the command line options. It checks if an option is + * valid, and if so, it calls the appropriate method in the UserInterface, + * that reflects the global setting of DOL. + */ +public class Options { + + /** + * Parse the command-line arguments, creating models as specified. Then + * execute each model that contains a manager. + * + * @param args + * The command-line arguments. + * @exception IllegalArgumentException + * MyException If such and such occurs + * @throws IllegalArgumentException + * if an illegal argument is found on the command line. + */ + public Options(String args[]) throws IllegalArgumentException, NumberFormatException { + _ui = UserInterface.getInstance(); + if(args != null) { + _parseArgs(args); + } + } + + /** + * Parse the command-line arguments. + * + * @param args The arguments to be parsed + * + * @throws IllegalArgumentException + * if an illegal argument is found on the command line. + */ + protected void _parseArgs(String args[]) throws IllegalArgumentException, + NumberFormatException { + if(args.length > 0) { + for(int i = 0; i < args.length; i++ ) { + String arg = args[i]; + if(_parseArg(arg) == false ) { + if(arg.startsWith("-") && i < args.length - 1 ) { + if(arg.equals("--platform") || arg.equals("-p") ) { + _ui.setPlatformFileName(args[++i]); + } else if(arg.equals("--processnetwork") || arg.equals("-P") ) { + _ui.setNetworkFileName(args[++i]); + } else if(arg.equals("--mapping") || arg.equals("-m") ) { + _ui.setMappingFileName(args[++i]); + } else if(arg.equals("--scheduler") || arg.equals("-s") ) { + _ui.setSchedulerFileName(args[++i]); + } else if(arg.equals("--systemC") || arg.equals("-C") ) { + _ui.setCodeDirectoryName(args[++i]); + _ui.setSystemCFlag(); + } else if(arg.equals("--HdS") || arg.equals("-H") ) { + _ui.setHdsCodeDirectoryName(args[++i]); + _ui.setHdsFlag(); + } else if(arg.startsWith("--rtems") || arg.startsWith("-R") ) { + String bsp = arg; + bsp = bsp.replaceAll("--rtems" , ""); + bsp = bsp.replaceAll("-R", ""); + if (bsp.equals("")) { + _ui.setRtemsBSP("pc386"); + } else if (bsp.equals("pc386") || bsp.equals("mparm")) { + _ui.setRtemsBSP(bsp); + } else { + throw new IllegalArgumentException( + "Board support package \"" + bsp + + "\" not supported by code generation."); + } + _ui.setRtemsCodeDirectoryName(args[++i]); + _ui.setRtemsFlag(); + } else if(arg.equals("--PaF") || arg.equals("-PF") ) { + _ui.setPipeAndFilterCodeDirectoryName(args[++i]); + _ui.setPipeAndFilterFlag(); + } else if(arg.equals("--protothread") || arg.equals("-PT") ) { + _ui.setProtothreadCodeDirectoryName(args[++i]); + _ui.setProtothreadFlag(); + } else if(arg.equals("--dotty") || arg.equals("-D")) { + _ui.setDottyFileName(args[++i]); + } else if(arg.equals("--profiling") || arg.equals("-T")) { + _ui.setTraceName(args[++i]); + _ui.setProfilingFlag(); + } else if (arg.equals("--vsplog") || arg.equals("-L")) { + _ui.setVspLogFileName(args[++i]); + _ui.setVspLogFlag(); + } else if (arg.equals("--workload") || arg.equals("-W")) { + _ui.setWorkloadFileName(args[++i]); + _ui.setWorkloadFlag(); + } else if(arg.equals("--xmlGen") || arg.equals("-G")) { + _ui.setOutputFileName(args[++i]); + _ui.setXmlGenFlag(); + } else if(arg.equals("--cbe") || arg.equals("-CBE")) { + _ui.setCbeCodeDirectoryName(args[++i]); + _ui.setCbeFlag(); + } else if(arg.equals("--yapi") || arg.equals("-Y")) { + _ui.setYapiCodeDirectoryName(args[++i]); + _ui.setYapiFlag(); + } else { + throw new IllegalArgumentException("Unrecognized option: " + arg); + } + } else { + throw new IllegalArgumentException("Unrecognized option: " + arg); + } + } + } + } else { + throw new IllegalArgumentException(_usage()); + } + } + + /** + * Parse a single commandline argument. + * + * @param arg commandline argument + * @return true if the argument is understood, false otherwise + * @throws IllegalArgumentExcecption thrown if an illegal argument is + * found on the command line. + */ + protected boolean _parseArg(String arg) throws IllegalArgumentException { + if(arg.equals("--help") || arg.equals("-h") ) { + //throw new IllegalArgumentException(_usage()); + System.out.println(_usage() ); + System.exit(0); + } else if(arg.equals("--version") || arg.equals("-V") ) { + System.out.println("DOL version 0.0.1\n"); + System.exit(0); + } else if(arg.equals("--verbose") || arg.equals("-v") ) { + _ui.setVerboseFlag(); + } else if(arg.equals("--check") || arg.equals("-c")) { + _ui.setCheckFlag(); + } else if(arg.equals("--debug")) { + _ui.setDebugFlag(); + } else if(arg.equals("")) { // Ignore blank argument. + } else { // Argument not recognized. + return false; + } + return true; + } + + /** + * Return a string summarizing the command-line arguments. + * + * @return A usage string. + */ + protected String _usage() { + String result = "Usage: " + _commandTemplate + "\n\n" + + "Options:\n"; + + int i; + for(i = 0; i < _commandOptions.length; i++) { + result += " " + _commandOptions[i][0] + "\tabbr[" + + _commandOptions[i][1] + " " + _commandOptions[i][2] + + "]\n"; + } + result += "\nBoolean flags:\n"; + for(i = 0; i < _commandFlags.length; i++) { + result += " " + _commandFlags[i][0] + "\tabbr[" + + _commandFlags[i][1] + "]\n"; + } + return result; + } + + /** + * The command-line options that are either present or not. Give the full + * name preceded with '--' and abbreviated version. + */ + protected String _commandFlags[][] = { + { "--check ", "-c" }, + { "--flatten ", "-F" }, + { "--help ", "-h" }, + { "--version ", "-V" }, + { "--verbose ", "-v" }, + { "--debug ", "none" }, + }; + + /** + * The command-line options that take arguments. + */ + protected String _commandOptions[][] = { + { "--platform ", "-p ", "" }, + { "--processnetwork", "-P ", "" }, + { "--mapping ", "-m ", "" }, + { "--scheduler ", "-s ", "" }, + { "--dotty ", "-D ", "" }, + { "--xmlGen ", "-G", "" }, + { "--systemC ", "-C ", "" }, + { "--HdS ", "-H ", "" }, + { "--PaF ", "-PF", "" }, + { "--rtems ", "-R", "" }, + { "--protothread ", "-PT", "" }, + { "--profiling ", "-T", "" }, + { "--vsplog ", "-L", "" }, + { "--cbe ", "-CBE", "" }, + { "--yapi ", "-Y", "" } + }; + + /** + * The form of the command line. + */ + protected String _commandTemplate = "dol [ options ]"; + + /** + * The UserInterface object. + */ + protected UserInterface _ui = null; +} diff --git a/dol/src/dol/main/UserInterface.java b/dol/src/dol/main/UserInterface.java new file mode 100644 index 0000000..f9145ea --- /dev/null +++ b/dol/src/dol/main/UserInterface.java @@ -0,0 +1,771 @@ +/* $Id: UserInterface.java 203 2010-10-11 08:59:47Z dchokshi $ */ +package dol.main; + +import java.util.ResourceBundle; + +/** + * Class to store commandline arguments and flags. + */ +public class UserInterface { + + /** + * Get a single instance of the UserInterface object. + */ + private final static UserInterface _instance = new UserInterface(); + + + // the platform file name + private String _platformFileName = null; + + // the process network file name + private String _networkFileName = null; + + // the mapping file name + private String _mappingFileName = null; + + // the scheduler file name + private String _schedulerFileName = null; + + // the dotty file name + private String _dottyFileName = "dotty.dot"; + + // the systemc directory name + private String _codeDirectoryName = "nonamePackage"; + + // the pipeandfilter directory name + private String _pipeCodeDirectoryName = "nonamePipePackage"; + + // the hds directory name + private String _hdsCodeDirectoryName = "nonameHdsPackage"; + + // the RTEMS directory name + private String _rtemsCodeDirectoryName = "nonameHdsPackage"; + + // the RTEMS board support package for which code is generated + private String _rtemsBSP = "pc386"; + + // the protothread directory name + private String _protothreadCodeDirectoryName = "nonameHdsPackage"; + + // the CBE directory name + private String _cbeCodeDirectoryName = "nonameCbePackage"; + + // the Yapi directory name + private String _yapiCodeDirectoryName = "nonameYapiPackage"; + + + // trace filename + private String _traceName = ""; + + // the xml generation flag + private boolean _xmlGen = false; + + // the trace flag + private boolean _profiling = false; + + // the verbose flag + private boolean _verbose = false; + + // the vsp log flag + private boolean _vsplog = false; + + // the name of the vsp log file + private String _vspLogFileName = ""; + + // the workload annotation flag + private boolean _workload = false; + + // the name of the workload file produces by Matlab + private String _workloadFileName = ""; + + // the SystemC flag + private boolean _systemC = false; + + // the PipeAndFilter flag + private boolean _pipeAndFilter = false; + + // the HdS flag + private boolean _hds = false; + + // the RTEMS flag + private boolean _rtems = false; + + // the CBE flag + private boolean _cbe = false; + + // the YAPI flag + private boolean _yapi = false; + + // the RTEMS flag + private boolean _protothread = false; + + // the debug flag + private boolean _debug = false; + + // the dotty flag + private boolean _dotty = false; + + // the check flag + private boolean _check = false; + + // the path finder flag + private boolean _archiPaths = true; + + // the basepath name + private String _basePath = "."; + + // the filename + private String _outputFileName = ""; + + // ResourceBundle + private ResourceBundle _rb; + private String _rbFileName = "dol"; + + + public final String getMySystemCLib() { + /* + return _rb.getString("DOL_path") + getDelimiter() + + "src" + getDelimiter() + "dol" + getDelimiter() + + "visitor" + getDelimiter() + "systemC" + getDelimiter() + + "lib"; + */ + + return this.getClass().getResource( + "/dol/visitor/systemC/lib").getFile(); + } + + public final String getVisitorDir(){ + return this.getClass().getResource("/dol/visitor/").getFile(); + } + + public final String getDOLPath() { return _rb.getString("DOL_path"); } + public final String getSystemCINC(){return _rb.getString("SYSTEMC_INC");} + public final String getSystemCLIB(){return _rb.getString("SYSTEMC_LIB");} + + /** + * returns the singleton instance of this class. + * + * @return The instance value + */ + public final static UserInterface getInstance() { + return _instance; + } + + /** + * Get the name of the platform file. + * + * @return The platformFileName value + */ + public final String getPlatformFileName() { + return _platformFileName; + } + + /** + * Set the name of the platform file. + * + * @param platformFileName + * The new platformFileName value + */ + public final void setPlatformFileName(String platformFileName) { + _platformFileName = platformFileName; + } + + /** + * Get the name of the process network file. + * + * @return The networkFileName value + */ + public final String getNetworkFileName() { + return _networkFileName; + } + + /** + * Set the name of the process network file. + * + * @param networkFileName + * The new networkFileName value + */ + public final void setNetworkFileName(String networkFileName) { + _networkFileName = networkFileName; + } + + /** + * Get the name of the mapping file. + * + * @return The mappingFileName value + */ + public final String getMappingFileName() { + return _mappingFileName; + } + + /** + * Set the name of the mapping file. + * + * @param mappingFileName + * The new mappingFileName value + */ + public final void setMappingFileName(String mappingFileName) { + _mappingFileName = mappingFileName; + } + + + /** + * Get the name of the scheduler file. + * + * @return The schedulerFileName value + */ + public final String getSchedulerFileName() { + return _schedulerFileName; + } + + /** + * Set the name of the scheduler file. + * + * @param schedulerFileName + * The new schedulerFileName value + */ + public final void setSchedulerFileName(String schedulerFileName) { + _schedulerFileName = schedulerFileName; + } + + + /** + * Get the name of the dotty file.. + * + * @return The dottyFileName value + */ + public final String getDottyFileName() { + return _dottyFileName; + } + + /** + * Set the name of the directory where the SystemC code shall be generated. + * + * @param dottyFileName The dotty file name. + */ + public final void setDottyFileName(String dottyFileName) { + _dottyFileName = dottyFileName; + setDottyFlag(); + } + + + /** + * Get the name of the SystemC directory. + * + * @return The directory name of the SystemC directory + */ + public final String getCodeDirectoryName() { + return _codeDirectoryName; + } + + /** + * Set the name of the directory where the SystemC code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setCodeDirectoryName(String codeDirectoryName) { + _codeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the PipeAndFilter directory. + * + * @return The directory name of the PipeAndFilter directory + */ + public final String getPipeAndFilterCodeDirectoryName() { + return _pipeCodeDirectoryName; + } + + /** + * Set the name of the directory where the Hds code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setPipeAndFilterCodeDirectoryName(String codeDirectoryName) { + _pipeCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the Hds directory. + * + * @return The directory name of the Hds directory + */ + public final String getHdsCodeDirectoryName() { + return _hdsCodeDirectoryName; + } + + /** + * Set the name of the directory where the Hds code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setHdsCodeDirectoryName(String codeDirectoryName) { + _hdsCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the protothread directory. + * + * @return The directory name of the protohread directory + */ + public final String getProtothreadCodeDirectoryName() { + return _protothreadCodeDirectoryName; + } + + /** + * Set the name of the directory where the protothread code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setProtothreadCodeDirectoryName(String codeDirectoryName) { + _protothreadCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the CBE directory. + * + * @return The directory name of the CBE directory + */ + public final String getCbeCodeDirectoryName() { + return _cbeCodeDirectoryName; + } + + /** + * Set the name of the directory where the CBE code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setCbeCodeDirectoryName(String codeDirectoryName) { + _cbeCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the YAPI directory. + * + * @return The directory name of the CBE directory + */ + public final String getYapiCodeDirectoryName() { + return _yapiCodeDirectoryName; + } + + /** + * Set the name of the directory where the YAPI code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setYapiCodeDirectoryName(String codeDirectoryName) { + _yapiCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the RTEMS directory. + * + * @return The directory name of the RTEMS directory + */ + public final String getRtemsCodeDirectoryName() { + return _rtemsCodeDirectoryName; + } + + /** + * Set the name of the directory where the RTEMS code shall be generated. + * + * @param codeDirectoryName The code directory + */ + public final void setRtemsCodeDirectoryName(String codeDirectoryName) { + _rtemsCodeDirectoryName = codeDirectoryName; + } + + /** + * Get the name of the RTEMS board support package. + * + * @return The name of the board support package + */ + public final String getRtemsBSP() { + return _rtemsBSP; + } + + /** + * Set the name of the board support package for which RTEMS code shall + * be generated. + * + * @param bsp board support package for which code is generated + * currently supported: pc386, mparm + */ + public final void setRtemsBSP(String bsp) { + _rtemsBSP = bsp; + } + + /** + * Get the status of the Verbose flag. + * + * @return verbose flag value + */ + public final boolean getVerboseFlag() { + return _verbose; + } + + /** + * Set the Verbose flag. + */ + public final void setVerboseFlag() { + _verbose = true; + } + + /** + * Get the status of the Debug flag. + * + * @return debug flag value + */ + public final boolean getDebugFlag() { + return _debug; + } + + /** + * Set the debug flag. + */ + public final void setDebugFlag() { + _debug = true; + } + + /** + * Get the status of the SystemC flag + * + * @return The systemC flag value + */ + public final boolean getSystemCFlag() { + return _systemC; + } + + /** + * Set the SystemC flag. + */ + public final void setSystemCFlag() { + _systemC = true; + } + + /** + * Get the status of the PipeAndFilter flag. + * + * @return PipeAndFilter flag value + */ + public final boolean getPipeAndFilterFlag() { + return _pipeAndFilter; + } + + /** + * Set the PipeAndFilter flag. + */ + public final void setPipeAndFilterFlag() { + _pipeAndFilter = true; + } + + /** + * Get the status of the protothread flag. + * + * @return PipeAndFilter flag value + */ + public final boolean getProtothreadFlag() { + return _protothread; + } + + /** + * Set the protothread flag. + */ + public final void setProtothreadFlag() { + _protothread = true; + } + + /** + * Get the status of the HdS flag. + * + * @return The HdS flag value + */ + public final boolean getHdsFlag() { + return _hds; + } + + /** + * Set the HdS flag. + */ + public final void setHdsFlag() { + _hds = true; + } + + /** + * Get the status of the RTEMS flag. + * + * @return The HdS flag value + */ + public final boolean getRtemsFlag() { + return _rtems; + } + + /** + * Set the RTEMS flag. + */ + public final void setRtemsFlag() { + _rtems = true; + } + + /** + * Get the status of the CBE flag. + * + * @return The CBE flag value + */ + public final boolean getCbeFlag() { + return _cbe; + } + + /** + * Set the CBE flag. + */ + public final void setCbeFlag() { + _cbe = true; + } + + /** + * Set the YAPI flag. + */ + public final void setYapiFlag() { + _yapi = true; + } + + /** + * Get the status of the YAPI flag. + * + * @return The YAPI flag value + */ + public final boolean getYapiFlag() { + return _yapi; + } + + /** + * Get the status of the dotty flag + * + * @return The dotty Flag value + */ + public final boolean getDottyFlag() { + return _dotty; + } + + /** + * Sets the dotty flag + */ + public final void setDottyFlag() { + _dotty = true; + } + + /** + * Get the status of the profiling flag + * + * @return The profiling Flag value + */ + public final boolean getProfilingFlag() { + return _profiling; + } + + /** + * Sets the profiling flag + */ + public final void setProfilingFlag() { + _profiling = true; + } + + /** + * Get the status of the vsp log flag. + * + * @return The vsp log value + */ + public final boolean getVspLogFlag() { + return _vsplog; + } + + /** + * Sets the vsp log flag. + */ + public final void setVspLogFlag() { + _vsplog = true; + } + + /** + * Get the status of the workload annotation flag. + * + * @return workload flag + */ + public final boolean getWorkloadFlag() { + return _workload; + } + + /** + * Sets the workload flag. + */ + public final void setWorkloadFlag() { + _workload = true; + } + + /** + * Get the status of the xml generation flag + * + * @return The xmlGen Flag value + */ + public final boolean getXmlGenFlag() { + return _xmlGen; + } + + /** + * Sets the xml generation flag + */ + public final void setXmlGenFlag() { + _xmlGen = true; + } + + /** + * Get the status of the check flag + * + * @return The check Flag value + */ + public final boolean getCheckFlag() { + return _check; + } + + /** + * Sets the check flag + */ + public final void setCheckFlag() { + _check = true; + } + + /** + * Get the status of the path finder flag + * + * @return The path finder Flag value + */ + public final boolean getPathsFlag() { + return _archiPaths; + } + + /** + * Sets the check flag + */ + public final void setPathsFlag() { + _archiPaths = true; + } + + /** + * Print a message to screen if the verbose flag has been selected with an + * end-of-line. + * + * @param s description that needs to printed. + */ + public void printVerbose(String s) { + if ( getVerboseFlag() ) { + System.out.print(s); + } + } + + /** + * Print a message to screen if the verbose flag has been selected. + * + * @param s description that needs to printed. + */ + public void printlnVerbose(String s) { + if( getVerboseFlag() ) { + System.out.println(s); + } + } + + + /** + * get the name of the output file name. + * + * @return The outputFileName value + */ + public final String getOutputFileName() { + return _outputFileName; + } + + /** + * set the name of the input file name. + * + * @param filename The new outputFileName value + */ + public final void setOutputFileName(String filename) { + _outputFileName = filename; + } + + /** + * get the base path name. + * + * @return The basePath value + */ + public final String getBasePath() { return _basePath; } + + /** + * sets the base path name. + * + * @param name + * The new basePath value + */ + public final void setBasePath(String name) { _basePath = name; } + + /** + * get the trace filename. + * + * @return The name value of trace file + */ + public final String getTraceName() { return _traceName; } + + /** + * sets the trace Filename. + * + * @param name trace filename + */ + public final void setTraceName(String name) { + _traceName = name; + } + + + /** + * Get the vsp log filename. + * + * @return name of the vsp log file + */ + public final String getVspLogFileName() { + return _vspLogFileName; + } + + /** + * Set the vsp log filename. + * + * @param name vsp log filename + */ + public final void setVspLogFileName(String name) { + _vspLogFileName = name; + } + + /** + * Get the workload filename. + * + * @return name workload filename + */ + public final String getWorkloadFileName() { + return _workloadFileName; + } + + /** + * Sets the workload filename. + * + * @param name workload filename + */ + public final void setWorkloadFileName(String name) { + _workloadFileName = name; + } + + /** + * Constructor. Private since only a single version may exist. + */ + private UserInterface() { + _rb = ResourceBundle.getBundle(_rbFileName); + } +} + diff --git a/dol/src/dol/main/package.html b/dol/src/dol/main/package.html new file mode 100644 index 0000000..d7cb962 --- /dev/null +++ b/dol/src/dol/main/package.html @@ -0,0 +1,20 @@ + + + + + + +DOL main program (commandline handling and dispatching of functions). + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/parser/xml/XmlErrorHandler.java b/dol/src/dol/parser/xml/XmlErrorHandler.java new file mode 100644 index 0000000..d1105bd --- /dev/null +++ b/dol/src/dol/parser/xml/XmlErrorHandler.java @@ -0,0 +1,45 @@ +/* $Id: XmlErrorHandler.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml; + +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * This class ... + */ + +public class XmlErrorHandler extends DefaultHandler { + + /** + * Empty constructor + */ + public XmlErrorHandler() { + super(); + } + + /** + * Treat validation errors as fatal + * + * @param e Description of the Parameter + * @exception SAXParseException MyException If such and such occurs + */ + public void error(SAXParseException e) + throws SAXParseException { + System.out.println("Error found: " + e.getMessage()); + throw e; + } + + /** + * Dump warnings too + * + * @param err Description of the Parameter + * @exception SAXParseException MyException If such and such occurs + */ + public void warning(SAXParseException err) + throws SAXParseException { + System.out.println("** Warning" + + ", line " + err.getLineNumber() + + ", uri " + err.getSystemId()); + System.out.println(" " + err.getMessage()); + } +} diff --git a/dol/src/dol/parser/xml/XmlParser.java b/dol/src/dol/parser/xml/XmlParser.java new file mode 100644 index 0000000..2032788 --- /dev/null +++ b/dol/src/dol/parser/xml/XmlParser.java @@ -0,0 +1,84 @@ +/* $Id: XmlParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.xerces.parsers.SAXParser; +import org.xml.sax.SAXException; + +import dol.main.UserInterface; + +/** + * Parser for DOL XML files. + */ +public class XmlParser extends org.xml.sax.helpers.DefaultHandler { + + /** + * Constructor. Initializes the parser. + */ + public XmlParser() { + super(); + try { + _parser = new SAXParser(); + _parser.setFeature("http://xml.org/sax/features/validation", + true); + _parser.setFeature("http://apache.org/xml/features/" + + "validation/schema", true); + _parser.setFeature("http://apache.org/xml/features/" + + "validation/schema-full-checking", true); + _parser.setErrorHandler(new XmlErrorHandler()); + _parser.setContentHandler(this); + _parser.setProperty("http://apache.org/xml/properties/schema/" + + "external-schemaLocation", + dol.util.SchemaLocation.getInternalSchemaLocation()); + _parser.setContentHandler(this); + _parser.setErrorHandler(new XmlErrorHandler()); + } catch (SAXException e) { + e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Return a absolute URL reference for the given URL. + * + * @param url url + * @return absolute URL reference + */ + protected String _makeAbsoluteURL(String url) + throws MalformedURLException { + URL baseURL; + String fileSep = System.getProperty("file.separator"); + String base = ""; + + // Replace all separators in given URL + url = url.replace(fileSep.charAt(0), '/'); + + // Add working directory if URL is not absolute + if (url.charAt(0) != '/' && !url.matches("^[A-Za-z]:/.*")) { + base = System.getProperty("user.dir") + '/'; + base = base.replace(fileSep.charAt(0), '/'); + if (base.charAt(0) != '/') { + base = "/" + base; + } + } + + // Add front slash to Windows style URLs + if (url.matches("^[A-Za-z]:/.*")) { + url = "/" + url; + } + + baseURL = new URL("file", null, base); + System.out.println(" -- full filename: " + + new URL(baseURL, url).toString()); + return new URL(baseURL, url).toString(); + } + + /** XML parser */ + protected SAXParser _parser; + + /** user interface used for messages for users */ + protected UserInterface _ui = UserInterface.getInstance(); +} diff --git a/dol/src/dol/parser/xml/archischema/ArchiXmlParser.java b/dol/src/dol/parser/xml/archischema/ArchiXmlParser.java new file mode 100644 index 0000000..38b1c05 --- /dev/null +++ b/dol/src/dol/parser/xml/archischema/ArchiXmlParser.java @@ -0,0 +1,187 @@ +/* $Id: ArchiXmlParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml.archischema; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import dol.datamodel.XmlTag; +import dol.datamodel.architecture.Architecture; +import dol.parser.xml.XmlParser; + +/** + * Parse architecture XML file. + */ +public class ArchiXmlParser extends XmlParser { + + /** + * Constructor. + */ + public ArchiXmlParser() { + super(); + _stack = new Stack(); + _xml2Archi = new Xml2Archi(); + } + + /** + * Do the parsing of an XML file describing an architecture. + * + * @param url The input XML file + * @return the architecture + */ + public Architecture doParse(String url) { + Architecture architecture = null; + System.out.println("Read architecture from XML file"); + + try { + String uri = _makeAbsoluteURL(url); + _ui.printlnVerbose("-- processing XML file: " + uri); + _ui.printlnVerbose("-- read XML file: "); + _stack.clear(); + _parser.parse(new InputSource(uri)); + architecture = (Architecture) _stack.pop(); + _ui.printlnVerbose(" [DONE] "); + } catch (SAXParseException err) { + System.out.println("** Parsing error, line " + + err.getLineNumber() + ", uri " + err.getSystemId()); + System.out.println(" " + err.getMessage()); + } catch (SAXException e) { + e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); + } + + architecture.computePaths(); + + System.out.println(" -- Architecture model from XML " + + "[Finished]"); + System.out.println(); + + return architecture; + } + + /** + * Action to be done while parsing a start element of an XML. + * + * @param elementName Description of the Parameter + * @param attributes Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void startElement(String namespaceURI, String localName, + String elementName, Attributes attributes) + throws SAXException { + Object val = null; + + if (elementName.equals(_xt.getArchiTag())) { + val = _xml2Archi.processArchitecture(attributes); + } else if (elementName.equals(_xt.getVariableTag())) { + val = _xml2Archi.processVariable(attributes); + } else if (elementName.equals(_xt.getProcessorTag())) { + val = _xml2Archi.processProcessor(attributes); + } else if (elementName.equals(_xt.getMemoryTag())) { + val = _xml2Archi.processMemory(attributes); + } else if (elementName.equals(_xt.getHWChannelTag())) { + val = _xml2Archi.processHWChannel(attributes); + } else if (elementName.equals(_xt.getConfigurationTag())) { + val = _xml2Archi.processConfiguration(attributes); + // simplified architecture + } else if (elementName.equals(_xt.getConnectionTag())) { + val = _xml2Archi.processConnection(attributes); + } else if (elementName.equals(_xt.getOriginTag())) { + val = _xml2Archi.processOrigin(attributes); + } else if (elementName.equals(_xt.getTargetTag())) { + val = _xml2Archi.processTarget(attributes); + } else if (elementName.equals(_xt.getNodeTag())) { + val = _xml2Archi.processNode(attributes); + } else if (elementName.equals(_xt.getPortTag())) { + val = _xml2Archi.processPort(attributes); + } else if (elementName.equals(_xt.getInPortTag())) { + val = _xml2Archi.processInputPort(attributes); + } else if (elementName.equals(_xt.getOutPortTag())) { + val = _xml2Archi.processOutputPort(attributes); + } else if (elementName.equals(_xt.getDuplexPortTag())) { + val = _xml2Archi.processInOutPort(attributes); + // detailed architecture + } else if (elementName.equals(_xt.getReadPathTag())) { + val = _xml2Archi.processReadPath(attributes); + } else if (elementName.equals(_xt.getWritePathTag())) { + val = _xml2Archi.processWritePath(attributes); + } else if (elementName.equals(_xt.getTXBufTag())) { + val = _xml2Archi.processTXBuf(attributes); + } else if (elementName.equals(_xt.getRXBufTag())) { + val = _xml2Archi.processRXBuf(attributes); + } else if (elementName.equals(_xt.getCHBufTag())) { + val = _xml2Archi.processCHBuf(attributes); + } else { + System.out.println("--Warning, DOL doesn't " + + "understand tag <" + elementName + "> "); + } + + if (val != null) { + _stack.push(val); + } + } + + /** + * Action to be done while parsing an end element of an XML. + * + * @param elementName Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void endElement(String namespaceURI, String localName, + String elementName) throws SAXException { + if (elementName.equals(_xt.getArchiTag())) { + _xml2Archi.processArchitecture(_stack); + } else if (elementName.equals(_xt.getVariableTag())) { + _xml2Archi.processVariable(_stack); + } else if (elementName.equals(_xt.getProcessorTag())) { + _xml2Archi.processProcessor(_stack); + } else if (elementName.equals(_xt.getMemoryTag())) { + _xml2Archi.processMemory(_stack); + } else if (elementName.equals(_xt.getHWChannelTag())) { + _xml2Archi.processHWChannel(_stack); + } else if (elementName.equals(_xt.getConfigurationTag())) { + _xml2Archi.processConfiguration(_stack); + // simplified architecture + } else if (elementName.equals(_xt.getConnectionTag())) { + _xml2Archi.processConnection(_stack); + } else if (elementName.equals(_xt.getOriginTag())) { + _xml2Archi.processOrigin(_stack); + } else if (elementName.equals(_xt.getTargetTag())) { + _xml2Archi.processTarget(_stack); + } else if (elementName.equals(_xt.getNodeTag())) { + _xml2Archi.processNode(_stack); + } else if (elementName.equals(_xt.getPortTag())) { + _xml2Archi.processPort(_stack); + } else if (elementName.equals(_xt.getInPortTag())) { + _xml2Archi.processInputPort(_stack); + } else if (elementName.equals(_xt.getOutPortTag())) { + _xml2Archi.processOutputPort(_stack); + } else if (elementName.equals(_xt.getDuplexPortTag())) { + _xml2Archi.processInOutPort(_stack); + // detailed architecture + } else if (elementName.equals(_xt.getReadPathTag())) { + _xml2Archi.processReadPath(_stack); + } else if (elementName.equals(_xt.getWritePathTag())) { + _xml2Archi.processWritePath(_stack); + } else if (elementName.equals(_xt.getTXBufTag())) { + _xml2Archi.processTXBuf(_stack); + } else if (elementName.equals(_xt.getRXBufTag())) { + _xml2Archi.processRXBuf(_stack); + } else if (elementName.equals(_xt.getCHBufTag())) { + _xml2Archi.processCHBuf(_stack); + } + } + + /** stack containing the generated objects */ + protected Stack _stack; + + /** actions to be taken while parsing every XML element */ + protected Xml2Archi _xml2Archi; + + /** */ + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/parser/xml/archischema/Xml2Archi.java b/dol/src/dol/parser/xml/archischema/Xml2Archi.java new file mode 100644 index 0000000..49d7409 --- /dev/null +++ b/dol/src/dol/parser/xml/archischema/Xml2Archi.java @@ -0,0 +1,630 @@ +/* $Id: Xml2Archi.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml.archischema; + +import java.util.Stack; + +import org.xml.sax.Attributes; + +import dol.datamodel.architecture.ArchiConnection; +import dol.datamodel.architecture.ArchiResource; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Configuration; +import dol.datamodel.architecture.HWChannel; +import dol.datamodel.architecture.Memory; +import dol.datamodel.architecture.Node; +import dol.datamodel.architecture.PortNode; +import dol.datamodel.architecture.Processor; +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.Variable; +import dol.datamodel.architecture.WritePath; + +/** + * Parse architecture XML file. + */ +public class Xml2Archi { + + /** + * Constructor. + */ + public Xml2Archi() { + } + + /** + * Process the start of the Architecture tag in the XML. + * + * @param attributes attributes of the tag + * @return an Architecture object + */ + public Architecture processArchitecture(Attributes attributes) { + Architecture arch = new Architecture(attributes.getValue("name")); + return arch; + + } + + /** + * Process the end of the Architecture tag in the XML. + * + * @param stack + */ + public void processArchitecture(Stack stack) { + } + + /** + * Process the start of a processor tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a processor object. + */ + public Processor processProcessor(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + String type = (String) attributes.getValue("type"); + Processor processor = new Processor(name); + if (basename != null) processor.setBasename(basename); + if (range != null) processor.setRange(range); + if (type != null) processor.setType(type); + return processor; + } + + /** + * Process the end of a processor tag in the XML. + * + * @param stack + */ + public void processProcessor(Stack stack) { + Processor p = (Processor) stack.pop(); + + if (stack.peek() instanceof ReadPath) { + ReadPath r = (ReadPath) stack.peek(); + Processor pro = ((Architecture)stack.elementAt(0)). + getProcessor(p.getName()); + if (pro != null) { + r.setProcessor(pro); + } + else { + undefinedReference("ReadPath", r.getName(), + "processor", p.getName()); + } + } else if (stack.peek() instanceof WritePath) { + WritePath w = (WritePath) stack.peek(); + Processor pro = ((Architecture)stack.elementAt(0)). + getProcessor(p.getName()); + if (pro != null) { + w.setProcessor(pro); + } + else { + undefinedReference("ReadPath", w.getName(), + "processor", p.getName()); + } + } else if (stack.peek() instanceof Architecture) { + Architecture a = (Architecture)stack.peek(); + p.setParentResource(a); + a.getProcessorList().add(p); + } + } + + /** + * Process the start of a memory tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a memory object. + */ + public Memory processMemory(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + String type = (String) attributes.getValue("type"); + Memory memory = new Memory(name); + if (basename != null) memory.setBasename(basename); + if (range != null) memory.setRange(range); + if (type != null) memory.setRange(type); + return memory; + } + + /** + * Process the end of a memory tag in the XML. + * + * @param stack + */ + public void processMemory(Stack stack) { + Memory m = (Memory) stack.pop(); + Architecture a = (Architecture)stack.peek(); + a.getMemoryList().add(m); + m.setParentResource(a); + } + + /** + * Process the start of a hw_channel tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a hw_chnannel object. + */ + public HWChannel processHWChannel(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + String type = (String) attributes.getValue("type"); + HWChannel hw_channel = new HWChannel(name); + if (basename != null) hw_channel.setBasename(basename); + if (range != null) hw_channel.setRange(range); + if (type != null) hw_channel.setType(type); + return hw_channel; + } + + /** + * Process the end of an hw_channel tag in the XML. + * + * @param stack + */ + public void processHWChannel(Stack stack) { + HWChannel c = (HWChannel) stack.pop(); + if (stack.peek() instanceof Architecture) { + Architecture a = (Architecture)stack.peek(); + a.getHWChannelList().add(c); + c.setParentResource(a); + } else if (stack.peek() instanceof WritePath) { + WritePath w = (WritePath)stack.peek(); + HWChannel ch = ((Architecture)stack.elementAt(0)). + getHWChannel(c.getName()); + if (ch != null) { + w.getHWChannelList().add(ch); + } + else { + undefinedReference("WritePath", w.getName(), + "hwchannel", c.getName()); + } + } else if (stack.peek() instanceof ReadPath) { + ReadPath r = (ReadPath)stack.peek(); + HWChannel ch = ((Architecture)stack.elementAt(0)). + getHWChannel(c.getName()); + if (ch != null) { + r.getHWChannelList().add(ch); + } + else { + undefinedReference("ReadPath", r.getName(), + "hwchannel", c.getName()); + } + } + } + + /** + * Process the start of a node tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a node object. + */ + public Node processNode(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + Node node = new Node(name); + if (basename != null) node.setBasename(basename); + if (range != null) node.setRange(range); + return node; + } + + /** + * Process the end of an node tag in the XML. + * + * @param stack + */ + public void processNode(Stack stack) { + Node n = (Node) stack.pop(); + ArchiResource r = (ArchiResource)stack.peek(); + r.getNodeList().add(n); + } + + /** + * Process the start of a origin tag in the XML. + * + * @param attributes attributes of the tag + * @return a ArchiResource object + */ + public ArchiResource processOrigin(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + ArchiResource resource = new ArchiResource(name); + if (basename != null) resource.setBasename(basename); + + return resource; + } + + /** + * Process the end of a origin tag in the XML. + * + * @param stack + */ + public void processOrigin(Stack stack) { + ArchiResource resource = (ArchiResource) stack.pop(); + ArchiConnection connection = (ArchiConnection) stack.peek(); + connection.setOrigin(resource); + } + + /** + * Process the start of a target tag in the XML. + * + * @param attributes attributes of the tag + * @return a ArchiResource object + */ + public ArchiResource processTarget(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + ArchiResource resource = new ArchiResource(name); + if (basename != null) resource.setBasename(basename); + + return resource; + } + + /** + * Process the end of a target tag in the XML. + * + * @param stack + */ + public void processTarget(Stack stack) { + ArchiResource resource = (ArchiResource) stack.pop(); + ArchiConnection connection = (ArchiConnection) stack.peek(); + connection.setTarget(resource); + } + + /** + * Process the start of a connection tag in the XML. + * + * @param attributes attributes of the tag + * @return a ArchiConnection object + */ + public ArchiConnection processConnection(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + ArchiConnection connection = new ArchiConnection(name); + if (basename != null) connection.setBasename(basename); + + return connection; + } + + /** + * Process the end of a connection tag in the XML. + * + * @param stack + */ + public void processConnection(Stack stack) { + ArchiConnection connection = (ArchiConnection) stack.pop(); + Architecture architecture = (Architecture)stack.peek(); + architecture.getConnectionList().add(connection); + connection.setParentResource(architecture); + } + + /** + * Process the start of an inputport/outputport/port tag in the XML. + * + * @param attributes attributes of the tag + * @return a PortNode object + */ + public PortNode processInputPort(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + + PortNode port = new PortNode(name, PortNode.INPORT); + + if (basename != null) + port.setBasename(basename); + else + port.setBasename(name); + + if (range != null) + port.setRange(range); + + return port; + } + + public PortNode processOutputPort(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + + PortNode port = new PortNode(name, PortNode.OUTPORT); + + if (basename != null) + port.setBasename(basename); + else + port.setBasename(name); + + if (range != null) + port.setRange(range); + + return port; + } + + // duplexport + public PortNode processInOutPort(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + + PortNode port = new PortNode(name, PortNode.INOUTPORT); + + if (basename != null) + port.setBasename(basename); + else + port.setBasename(name); + + if (range != null) + port.setRange(range); + + return port; + } + + public PortNode processPort(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + + PortNode port = new PortNode(name); + + if (basename != null) + port.setBasename(basename); + else + port.setBasename(name); + + if (range != null) + port.setRange(range); + + return port; + } + + /** + * Process the end of an inputport/outputport/duplexport/port tag in the XML. + * + * @param stack + */ + public void processInputPort(Stack stack) { + PortNode port = (PortNode) stack.pop(); + Node node = (Node) stack.peek(); + port.setNode(node); + node.getPortList().add(port); + } + + public void processOutputPort(Stack stack) { + PortNode port = (PortNode) stack.pop(); + Node node = (Node) stack.peek(); + port.setNode(node); + node.getPortList().add(port); + } + + public void processInOutPort(Stack stack) { + PortNode port = (PortNode) stack.pop(); + Node node = (Node) stack.peek(); + port.setNode(node); + node.getPortList().add(port); + } + + public void processPort(Stack stack) { + PortNode port = (PortNode) stack.pop(); + Node node = (Node) stack.peek(); + port.setNode(node); + node.getPortList().add(port); + } + + /** + * Process the start of a variable tag in the XML. + * + * @param attributes attributes of the tag + * @return a Variable object + */ + public Variable processVariable(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Variable variable = null; + variable = new Variable(name); + variable.setValue(Integer.parseInt(value)); + + return variable; + } + + /** + * Process the end of a variable tag in the XML. + * + * @param stack + */ + public void processVariable(Stack stack) { + Variable variable = (Variable) stack.pop(); + Architecture architecture = (Architecture)stack.peek(); + variable.setParentResource(architecture); + architecture.getVarList().add(variable); + } + + /** + * Process the start of a configuration tag in the XML. + * + * @param attributes attributes of the tag + * @return a Configuration object + */ + public Configuration processConfiguration(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Configuration config = new Configuration(name); + config.setValue(value); + return config; + } + + /** + * Process the end of a configuration tag in the XML. + * + * @param stack + */ + public void processConfiguration(Stack stack) { + Configuration config = (Configuration) stack.pop(); + ArchiResource res = (ArchiResource)stack.peek(); + res.getCfgList().add(config); + } + + /** + * Process the start of a readPath tag in the XML. + * + * @param attributes attributes of the tag + * @return a ReadPath object + */ + public ReadPath processReadPath(Attributes attributes) { + String name = (String) attributes.getValue("name"); + ReadPath readpath = new ReadPath(name); + return readpath; + } + + /** + * Process the end of a readpath tag in the XML. + * + * @param stack + */ + public void processReadPath(Stack stack) { + ReadPath readpath = (ReadPath) stack.pop(); + Architecture res = (Architecture)stack.peek(); + res.getReadPathList().add(readpath); + } + + /** + * Process the start of a WritePath tag in the XML. + * + * @param attributes attributes of the tag + * @return a WritePath object + */ + public WritePath processWritePath(Attributes attributes) { + String name = (String) attributes.getValue("name"); + WritePath writepath = new WritePath(name); + return writepath; + } + + /** + * Process the end of a WritePath tag in the XML. + * + * @param stack + */ + public void processWritePath(Stack stack) { + WritePath writepath = (WritePath) stack.pop(); + Architecture res = (Architecture)stack.peek(); + res.getWritePathList().add(writepath); + } + + /** + * Process the start of a TXBuf tag in the XML. + * + * @param attributes attributes of the tag + * @return a TXBuf object + */ + public String processTXBuf(Attributes attributes) { + return attributes.getValue("name"); + } + + /** + * Process the end of a TXBuf tag in the XML. + * + * @param stack + */ + public void processTXBuf(Stack stack) { + String txBuf = (String) stack.pop(); + WritePath res = (WritePath)stack.peek(); + Memory mem = ((Architecture)res.getProcessor().getParentResource()).getMemory(txBuf); + if (mem != null) { + res.setTXBuf(mem); + } + else { + undefinedReference("ReadPath", res.getName(), + "transmit buffer", txBuf); + } + } + + /** + * Process the start of a RXBuf tag in the XML. + * + * @param attributes attributes of the tag + * @return a RXBuf object + */ + public String processRXBuf(Attributes attributes) { + return attributes.getValue("name"); + } + + /** + * Process the end of a RXBuf tag in the XML. + * + * @param stack + */ + public void processRXBuf(Stack stack) { + String rxBuf = (String) stack.pop(); + ReadPath res = (ReadPath)stack.peek(); + Memory mem = ((Architecture)res.getProcessor().getParentResource()).getMemory(rxBuf); + if (mem != null) { + res.setRXBuf(mem); + } + else { + undefinedReference("ReadPath", res.getName(), + "receive buffer", rxBuf); + } + } + + /** + * Process the start of a CHBuf tag in the XML. + * + * @param attributes attributes of the tag + * @return a CHBuf object + */ + public String processCHBuf(Attributes attributes) { + return attributes.getValue("name"); + } + + /** + * Process the end of a CHBuf tag in the XML. + * + * @param stack + */ + public void processCHBuf(Stack stack) { + String value= (String) stack.pop(); + if (stack.peek() instanceof ReadPath) { + ReadPath res = (ReadPath)stack.peek(); + Memory mem = ((Architecture)res.getProcessor(). + getParentResource()).getMemory(value); + if (mem != null) { + res.setCHBuf(mem); + } + else { + undefinedReference("ReadPath", res.getName(), + "channel buffer", value); + } + } + else if (stack.peek() instanceof WritePath) { + WritePath res = (WritePath)stack.peek(); + Memory mem = ((Architecture)res.getProcessor(). + getParentResource()).getMemory(value); + if (mem != null) { + res.setCHBuf(mem); + } + else { + undefinedReference("WritePath", res.getName(), + "channel buffer", value); + } + } + } + + + /** + * Write an error message and terminate program in case an + * architecture element is referenced which does not exist. + * + * @param elementType type of architecture element in which the error + * occurred + * @param elementName name of architecture element in which the error + * occurred + * @param referenceType type of referenced architecture element + * @param referenceName name of referenced architecture element + */ + protected void undefinedReference(String elementType, + String elementName, + String referenceType, + String referenceName) { + System.out.println("Error: " + elementType + " " + elementName + + " references " + referenceType + " " + referenceName + + " that has not been declared."); + System.out.println("Exit."); + System.exit(-1); + } + +} diff --git a/dol/src/dol/parser/xml/archischema/package.html b/dol/src/dol/parser/xml/archischema/package.html new file mode 100644 index 0000000..6a6b096 --- /dev/null +++ b/dol/src/dol/parser/xml/archischema/package.html @@ -0,0 +1,21 @@ + + + + + + +Architecture XML schema parser. +This package parses the architecture XML and stores the result in internal architectural data model. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/parser/xml/mapschema/MapXmlParser.java b/dol/src/dol/parser/xml/mapschema/MapXmlParser.java new file mode 100644 index 0000000..4849601 --- /dev/null +++ b/dol/src/dol/parser/xml/mapschema/MapXmlParser.java @@ -0,0 +1,211 @@ +/* $Id: MapXmlParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml.mapschema; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import dol.datamodel.XmlTag; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.ProcessNetwork; +import dol.parser.xml.XmlParser; + + +/** + * Parse mapping XML file. + */ +public class MapXmlParser extends XmlParser { + + /** + * Constructor. + * @param pn the process network the mapping is referring to + * @param arch the architecture the mapping is referring to + */ + public MapXmlParser(ProcessNetwork pn, Architecture arch) { + super(); + _stack = new Stack(); + _xml2Map = new Xml2Map(pn, arch); + } + + /** + * Do the parsing of an XML file describing a mapping. + * + * @param url The input XML file + * @return the mapping + */ + public Mapping doParse(String url) { + Mapping map = null; + System.out.println("Read mapping from XML file"); + + try { + String uri = _makeAbsoluteURL(url); + _ui.printlnVerbose("-- processing XML file: " + uri); + _ui.printlnVerbose("-- read XML file: "); + _stack.clear(); + _parser.parse(new InputSource(uri)); + map = (Mapping) _stack.pop(); + _ui.printlnVerbose(" [DONE] "); + } catch (SAXParseException err) { + System.out.println("** Parsing error, line " + + err.getLineNumber() + ", uri " + err.getSystemId()); + System.out.println(" " + err.getMessage()); + } catch (SAXException e) { + //e.printStackTrace(); + e.getMessage(); + System.exit(-1); + } catch (Throwable t) { + t.printStackTrace(); + } + + System.out.println(" -- Mapping from XML " + + "[Finished]"); + System.out.println(); + + return map; + } + + /** + * Action to be done while parsing a start element of an XML. + * + * @param elementName Description of the Parameter + * @param attributes Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void startElement(String namespaceURI, String localName, + String elementName, Attributes attributes) + throws SAXException { + Object val = null; + + if (elementName.equals(_xt.getMappingTag())) { + val = _xml2Map.processMapping(attributes); + } else if (elementName.equals(_xt.getVariableTag())) { + val = _xml2Map.processVariable(attributes); + } else if (elementName.equals(_xt.getBindingTag())) { + val = _xml2Map.processBinding(attributes); + } else if (elementName.equals(_xt.getOriginTag())) { + val = _xml2Map.processOrigin(attributes); + } else if (elementName.equals(_xt.getProcessTag())) { + val = _xml2Map.processProcess(attributes); + } else if (elementName.equals(_xt.getProcessorTag())) { + val = _xml2Map.processProcessor(attributes); + } else if (elementName.equals(_xt.getSWChannelTag())) { + val = _xml2Map.processChannel(attributes); + } else if (elementName.equals(_xt.getReadPathTag())) { + val = _xml2Map.processReadPath(attributes); + } else if (elementName.equals(_xt.getWritePathTag())) { + val = _xml2Map.processWritePath(attributes); + } else if (elementName.equals(_xt.getScheduleTag())) { + val = _xml2Map.processSchedule(attributes); + } else if (elementName.equals(_xt.getResourceTag())) { + val = _xml2Map.processResource(attributes); + } else if (elementName.equals(_xt.getConfigurationTag())) { + val = _xml2Map.processConfiguration(attributes); + } else { + System.out.println(" -- Warning, DOL doesn't " + + "understand tag <" + elementName + "> "); + } + + if (val != null) { + _stack.push(val); + } + + consistencyCheck(elementName, attributes, val); + } + + + /** + * Action to be done while parsing an end element of an XML. + * + * @param elementName Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void endElement(String namespaceURI, String localName, + String elementName) throws SAXException { + if (elementName.equals(_xt.getMappingTag())) { + _xml2Map.processMapping(_stack); + } else if (elementName.equals(_xt.getVariableTag())) { + _xml2Map.processVariable(_stack); + } else if (elementName.equals(_xt.getBindingTag())) { + _xml2Map.processBinding(_stack); + } else if (elementName.equals(_xt.getOriginTag())) { + _xml2Map.processOrigin(_stack); + /* + } else if (elementName.equals(_xt.getTargetTag())) { + _xml2Map.processTarget(_stack); + */ + } else if (elementName.equals(_xt.getProcessTag())) { + _xml2Map.processProcess(_stack); + } else if (elementName.equals(_xt.getProcessorTag())) { + _xml2Map.processProcessor(_stack); + } else if (elementName.equals(_xt.getSWChannelTag())) { + _xml2Map.processChannel(_stack); + } else if (elementName.equals(_xt.getReadPathTag())) { + _xml2Map.processReadPath(_stack); + } else if (elementName.equals(_xt.getWritePathTag())) { + _xml2Map.processWritePath(_stack); + } else if (elementName.equals(_xt.getScheduleTag())) { + _xml2Map.processSchedule(_stack); + } else if (elementName.equals(_xt.getResourceTag())) { + _xml2Map.processResource(_stack); + } else if (elementName.equals(_xt.getConfigurationTag())) { + _xml2Map.processConfiguration(_stack); + } + } + + /** + * + */ + protected void consistencyCheck(String elementName, + Attributes attributes, Object val) { + boolean isConsistent = true; + + if (elementName.equals(_xt.getProcessorTag()) + && val == null) { + System.out.println(" -- Error: Could not find processor \"" + + attributes.getValue("name") + "\" in " + + "architecture."); + isConsistent = false; + } else if (elementName.equals(_xt.getProcessTag()) + && val == null) { + System.out.println(" -- Error: Could not find process \"" + + attributes.getValue("name") + "\" in " + + "process network."); + isConsistent = false; + } else if (elementName.equals(_xt.getSWChannelTag()) + && val == null) { + System.out.println(" -- Error: Could not find channel \"" + + attributes.getValue("name") + "\" in " + + "process network."); + isConsistent = false; + } else if (elementName.equals(_xt.getReadPathTag()) + && val == null) { + System.out.println(" -- Error: Could not find read path \"" + + attributes.getValue("name") + "\" in " + + "architecture."); + isConsistent = false; + } else if (elementName.equals(_xt.getWritePathTag()) + && val == null) { + System.out.println(" -- Error: Could not find write path \"" + + attributes.getValue("name") + "\" in " + + "architecture."); + isConsistent = false; + } + + if (!isConsistent) { + System.exit(-1); + } + } + + /** stack containing the generated objects */ + protected Stack _stack; + + /** actions to be taken while parsing every XML element */ + protected Xml2Map _xml2Map; + + /** */ + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/parser/xml/mapschema/Xml2Map.java b/dol/src/dol/parser/xml/mapschema/Xml2Map.java new file mode 100644 index 0000000..4d15666 --- /dev/null +++ b/dol/src/dol/parser/xml/mapschema/Xml2Map.java @@ -0,0 +1,381 @@ +package dol.parser.xml.mapschema; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import dol.datamodel.architecture.ArchiResource; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Processor; +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.WritePath; +import dol.datamodel.mapping.Binding; +import dol.datamodel.mapping.CommunicationBinding; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Configuration; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.mapping.Schedule; +import dol.datamodel.mapping.ScheduleEntry; +import dol.datamodel.mapping.SchedulingPolicy; +import dol.datamodel.mapping.Variable; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; + + +/** + * + */ +public class Xml2Map { + + /** + * Constructor. + * @param pn the process network the mapping is refering to + * @param arch the architecture the mapping is refering to + */ + public Xml2Map(ProcessNetwork pn, Architecture arch) { + _pn = pn; + _arch = arch; + } + + /** + * Process the start of the mapping tag in the XML. + * + * @param attributes attributes of the tag + * @return a Mapping object + */ + public Mapping processMapping(Attributes attributes) { + String name = (String) attributes.getValue("name"); + Mapping map = new Mapping(name); + map.setArch(_arch); + map.setPN(_pn); + return map; + } + + /** + * Process the end of the mapping tag in the XML. + * + * @param stack + */ + public void processMapping(Stack stack) { + } + + /** + * Process the start of a binding tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a Binding object. + */ + public Binding processBinding(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String type = (String) attributes.getValue( + "http://www.w3.org/2001/XMLSchema-instance", "type"); + Binding b = null; + // Create ComputationBinding or CommunicationBinding object + if(type.equals(Binding.COMPUTATION)) { + b = new ComputationBinding(name); + } else if(type.equals(Binding.COMMUNICATION)) { + b = new CommunicationBinding(name); + } + + if (basename != null) b.setBasename(basename); + + return b; + } + + /** + * Process the end of a binding tag in the XML. + * + * @param stack + */ + public void processBinding(Stack stack) throws SAXException + { + Binding bind = (Binding)stack.pop(); + Mapping map = (Mapping)stack.peek(); + bind.setParentResource(map); + + if(bind instanceof ComputationBinding) { + ComputationBinding cb = (ComputationBinding)bind; + Process process = cb.getProcess(); + Processor processor = cb.getProcessor(); + process.setProcessor(processor); + processor.getProcessList().add(process); + + map.getCompBindList().add(cb); + map.getProcessList().add(process); + if (!map.getProcessorList().contains(processor)) { + map.getProcessorList().add(processor); + } + + } else { + map.getCommBindList().add((CommunicationBinding)bind); + } + } + + /** + * Process the start of a origin tag in the XML. + * + * @param attributes attributes of the tag + * @return a ScheduleEntry object + */ + public ScheduleEntry processOrigin(Attributes attributes) { + String name = (String) attributes.getValue("name"); + ScheduleEntry schedEntry = new ScheduleEntry(name); + + // A scheduled resource is either a PN process + // or a SW channel. + Process process = _pn.getProcess(name); + if(process != null) { + schedEntry.setConsumer(process); + } else { + Channel channel = _pn.getChannel(name); + schedEntry.setConsumer(channel); + } + return schedEntry; + } + + /** + * Process the end of a origin tag in the XML. + * + * @param stack + */ + public void processOrigin(Stack stack) { + ScheduleEntry schedProcess = (ScheduleEntry) stack.pop(); + Schedule sched = (Schedule) stack.peek(); + sched.getEntryList().add(schedProcess); + schedProcess.setParentResource(sched); + } + + /** + * Process the start of a variable tag in the XML. + * + * @param attributes attributes of the tag + * @return a Variable object + */ + public Variable processVariable(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Variable variable = new Variable(name); + variable.setValue(Integer.parseInt(value)); + return variable; + } + + /** + * Process the end of a variable tag in the XML. + * + * @param stack + */ + public void processVariable(Stack stack) { + Variable variable = (Variable) stack.pop(); + Mapping map = (Mapping) stack.peek(); + variable.setParentResource(map); + map.getVarList().add(variable); + } + + /** + * Process the start of a process tag in the XML. + * + * @param attributes attributes of the tag + * @return a Process object + */ + public Process processProcess(Attributes attributes) { + String name = (String) attributes.getValue("name"); + Process process = _pn.getProcess(name); + return process; + } + + /** + * Process the end of a process tag in the XML. + * + * @param stack + */ + public void processProcess(Stack stack) { + Process p = (Process) stack.pop(); + ComputationBinding b = (ComputationBinding) stack.peek(); + b.setProcess(p); + } + + /** + * Process the start of a processor tag in the XML. + * + * @param attributes attributes of the tag + * @return a Processor object + */ + public Processor processProcessor(Attributes attributes) { + String name = (String) attributes.getValue("name"); + Processor processor = _arch.getProcessor(name); + return processor; + } + + /** + * Process the end of a processor tag in the XML. + * + * @param stack + */ + public void processProcessor(Stack stack) { + Processor p = (Processor) stack.pop(); + ComputationBinding b = (ComputationBinding) stack.peek(); + b.setProcessor(p); + } + + /** + * Process the start of a channel tag in the XML. + * + * @param attributes attributes of the tag + * @return a Channel object + */ + public Channel processChannel(Attributes attributes) { + String name = (String) attributes.getValue("name"); + Channel channel = _pn.getChannel(name); + return channel; + } + + /** + * Process the end of a channel tag in the XML. + * + * @param stack + */ + public void processChannel(Stack stack) { + Channel chan = (Channel) stack.pop(); + CommunicationBinding b = (CommunicationBinding) stack.peek(); + b.setChannel(chan); + } + + /** + * Process the start of a read path tag in the XML. + * + * @param attributes attributes of the tag + * @return a ReadPath object + */ + public ReadPath processReadPath(Attributes attributes) { + String name = (String) attributes.getValue("name"); + ReadPath path = _arch.getReadPath(name); + return path; + } + + /** + * Process the end of a read path tag in the XML. + * + * @param stack + */ + public void processReadPath(Stack stack) { + ReadPath path = (ReadPath) stack.pop(); + CommunicationBinding b = (CommunicationBinding) stack.peek(); + b.setReadPath(path); + } + + /** + * Process the start of a write path tag in the XML. + * + * @param attributes attributes of the tag + * @return a WritePath object + */ + public WritePath processWritePath(Attributes attributes) { + String name = (String) attributes.getValue("name"); + WritePath path = _arch.getWritePath(name); + return path; + } + + /** + * Process the end of a write path tag in the XML. + * + * @param stack + */ + public void processWritePath(Stack stack) { + WritePath path = (WritePath) stack.pop(); + CommunicationBinding b = (CommunicationBinding) stack.peek(); + b.setWritePath(path); + } + + /** + * Process the start of a schedule tag in the XML. + * + * @param attributes attributes of the tag + * @return a Schedule object + */ + public Schedule processSchedule(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String type = (String) attributes.getValue("type"); + String basename = (String) attributes.getValue("basename"); + + Schedule schedule = new Schedule(name); + schedule.setSchedPolicy(SchedulingPolicy.fromString(type)); + + if (basename != null) schedule.setBasename(basename); + + return schedule; + } + + /** + * Process the end of a schedule tag in the XML. + * + * @param stack + */ + public void processSchedule(Stack stack) { + Schedule sched = (Schedule) stack.pop(); + Mapping map = (Mapping) stack.peek(); + map.getScheduleList().add(sched); + sched.setParentResource(map); + } + + /** + * Process the start of a resource tag in the XML. + * + * @param attributes attributes of the tag + * @return a ArchiResource object + */ + public ArchiResource processResource(Attributes attributes) { + String name = (String) attributes.getValue("name"); + // We are either looking for a HW channel or for a processor. + ArchiResource resource = null; + if((resource = _arch.getHWChannel(name)) == null) { + resource = _arch.getProcessor(name); + } + return resource; + } + + /** + * Process the end of a resource tag in the XML. + * + * @param stack + */ + public void processResource(Stack stack) { + ArchiResource resource = (ArchiResource) stack.pop(); + Schedule schedule = (Schedule) stack.peek(); + schedule.setResource(resource); + } + + /** + * Process the start of a configuration tag in the XML. + * + * @param attributes attributes of the tag + * @return a Configuration object + */ + public Configuration processConfiguration(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Configuration config = new Configuration(name, value); + return config; + } + + /** + * Process the end of a configuration tag in the XML. + * + * @param stack + */ + public void processConfiguration(Stack stack) { + Configuration config = (Configuration) stack.pop(); + if(stack.peek() instanceof ScheduleEntry) { + ScheduleEntry schedProc = (ScheduleEntry) stack.peek(); + schedProc.getCfgList().add(config); + } else if(stack.peek() instanceof Schedule) { + Schedule sched = (Schedule) stack.peek(); + sched.getCfgList().add(config); + } + } + + protected ProcessNetwork _pn = null; + protected Architecture _arch = null; +} diff --git a/dol/src/dol/parser/xml/mapschema/package.html b/dol/src/dol/parser/xml/mapschema/package.html new file mode 100644 index 0000000..5fa0ab3 --- /dev/null +++ b/dol/src/dol/parser/xml/mapschema/package.html @@ -0,0 +1,21 @@ + + + + + + +Mapping XML schema parser. +This package parses the mapping XML and stores the result in internal architectural data model. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/parser/xml/package.html b/dol/src/dol/parser/xml/package.html new file mode 100644 index 0000000..50319b9 --- /dev/null +++ b/dol/src/dol/parser/xml/package.html @@ -0,0 +1,20 @@ + + + + + + +Parser for DOL specific XML files. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/parser/xml/pnschema/PNXmlParser.java b/dol/src/dol/parser/xml/pnschema/PNXmlParser.java new file mode 100644 index 0000000..70a4f8a --- /dev/null +++ b/dol/src/dol/parser/xml/pnschema/PNXmlParser.java @@ -0,0 +1,158 @@ +/* $Id: PNXmlParser.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml.pnschema; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import dol.datamodel.XmlTag; +import dol.datamodel.pn.ProcessNetwork; +import dol.parser.xml.XmlParser; + +/** + * Parse processnetwork XML file. + */ +public class PNXmlParser extends XmlParser { + + /** + * Constructor. + */ + public PNXmlParser() { + super(); + _stack = new Stack(); + _xml2PN = new Xml2PN(); + } + + /** + * Do the parsing of an XML file describing a processnetwork. + * + * @param url input XML file + * @return the processnetwork + */ + public ProcessNetwork doParse(String url) { + ProcessNetwork pn = null; + System.out.println("Read process network from XML file"); + + try { + String uri = _makeAbsoluteURL(url); + _ui.printlnVerbose("-- processing XML file: " + uri); + _ui.printlnVerbose("-- read XML file: "); + _stack.clear(); + _parser.parse(new InputSource(uri)); + pn = (ProcessNetwork) _stack.pop(); + _ui.printlnVerbose(" [DONE] "); + } catch (SAXParseException err) { + System.out.println("** Parsing error, line " + + err.getLineNumber() + ", uri " + err.getSystemId()); + System.out.println(" " + err.getMessage()); + } catch (SAXException e) { + e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); + } + + try { + pn.fillPortPeerInfo(); + } catch (Exception e) { + System.out.println("Exception\n " + e.getMessage()); + e.printStackTrace(System.out); + } + + System.out.println(" -- Process network model from XML " + + "[Finished]"); + System.out.println(); + + return pn; + } + + /** + * Action to be done while parsing a start element of an XML. + * + * @param namespaceURI + * @param localName + * @param elementName name of element + * @param attributes attributes of element + * @exception SAXException thrown when a parsing error occurs + */ + public void startElement(String namespaceURI, String localName, + String elementName, Attributes attributes) + throws SAXException { + Object val = null; + + if (elementName.equals(_xt.getPNTag())) { + val = _xml2PN.processPN(attributes); + } else if (elementName.equals(_xt.getVariableTag())) { + val = _xml2PN.processVariable(attributes); + } else if (elementName.equals(_xt.getProcessTag())) { + val = _xml2PN.processProcess(attributes); + } else if (elementName.equals(_xt.getSWChannelTag())) { + val = _xml2PN.processChannel(attributes); + } else if (elementName.equals(_xt.getConnectionTag())) { + val = _xml2PN.processConnection(attributes); + } else if (elementName.equals(_xt.getOriginTag())) { + val = _xml2PN.processOrigin(attributes); + } else if (elementName.equals(_xt.getTargetTag())) { + val = _xml2PN.processTarget(attributes); + } else if (elementName.equals(_xt.getPortTag())) { + val = _xml2PN.processPort(attributes); + } else if (elementName.equals(_xt.getSourceTag())) { + val = _xml2PN.processSource(attributes); + } else if (elementName.equals(_xt.getProfilingTag())) { + val = _xml2PN.processProfiling(attributes); + } else if (elementName.equals(_xt.getConfigurationTag())) { + val = _xml2PN.processConfiguration(attributes); + } else { + System.out.println("--Warning, DOL doesn't " + + "understand tag <" + elementName + "> "); + } + + if (val != null) { + _stack.push(val); + } + } + + /** + * Action to be done while parsing an end element of an XML. + * + * @param elementName Description of the Parameter + * @exception SAXException MyException If such and such occurs + */ + public void endElement(String namespaceURI, String localName, + String elementName) throws SAXException { + if (elementName.equals(_xt.getPNTag())) { + _xml2PN.processPN(_stack); + } else if (elementName.equals(_xt.getVariableTag())) { + _xml2PN.processVariable(_stack); + } else if (elementName.equals(_xt.getProcessTag())) { + _xml2PN.processProcess(_stack); + } else if (elementName.equals(_xt.getSWChannelTag())) { + _xml2PN.processChannel(_stack); + } else if (elementName.equals(_xt.getConnectionTag())) { + _xml2PN.processConnection(_stack); + } else if (elementName.equals(_xt.getOriginTag())) { + _xml2PN.processOrigin(_stack); + } else if (elementName.equals(_xt.getTargetTag())) { + _xml2PN.processTarget(_stack); + } else if (elementName.equals(_xt.getSourceTag())) { + _xml2PN.processSource(_stack); + } else if (elementName.equals(_xt.getProfilingTag())) { + _xml2PN.processProfiling(_stack); + } else if (elementName.equals(_xt.getConfigurationTag())) { + _xml2PN.processConfiguration(_stack); + } else if (elementName.equals(_xt.getPortTag())) { + _xml2PN.processPort(_stack); + } + } + + /** stack containing the generated objects */ + protected Stack _stack; + + /** actions to be taken while parsing every XML element */ + protected Xml2PN _xml2PN; + + /** */ + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/parser/xml/pnschema/Xml2PN.java b/dol/src/dol/parser/xml/pnschema/Xml2PN.java new file mode 100644 index 0000000..5586c16 --- /dev/null +++ b/dol/src/dol/parser/xml/pnschema/Xml2PN.java @@ -0,0 +1,457 @@ +/* $Id: Xml2PN.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.parser.xml.pnschema; + +import java.util.Stack; + +import org.xml.sax.Attributes; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Configuration; +import dol.datamodel.pn.Connection; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.datamodel.pn.Resource; +import dol.datamodel.pn.SourceCode; +import dol.datamodel.pn.Variable; + +/** + * + */ +public class Xml2PN { + + /** + * Constructor. + */ + public Xml2PN() { + } + + /** + * Process the start of the process network tag in the XML. + * + * @param attributes attributes of the tag + * @return a ProcessNetwork object + */ + public ProcessNetwork processPN(Attributes attributes) { + String name = (String) attributes.getValue("name"); + ProcessNetwork p = new ProcessNetwork(name); + return p; + } + + /** + * Process the end of the processnetwork tag in the XML. + * + * @param stack + */ + public void processPN(Stack stack) { + } + + /** + * Process the start of a process tag in the XML. + * + * @param attributes The attributes of the tag. + * @return a process object. + */ + public Process processProcess(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + Process process = new Process(name); + if (basename != null) process.setBasename(basename); + if (range != null) process.setRange(range); + return process; + } + + /** + * Process the end of a process tag in the XML. + * + * @param stack + */ + public void processProcess(Stack stack) { + Process p = (Process) stack.pop(); + ProcessNetwork r = (ProcessNetwork)stack.peek(); + r.getProcessList().add(p); + } + + /** + * Process the start of a origin tag in the XML. + * + * @param attributes attributes of the tag + * @return a Resource object + */ + public Resource processOrigin(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + Resource resource = new Resource(name); + if (basename != null) resource.setBasename(basename); + return resource; + } + + /** + * Process the end of a origin tag in the XML. + * + * @param stack + */ + public void processOrigin(Stack stack) { + Port port = (Port)stack.pop(); + Resource resource = (Resource)stack.pop(); + Connection connection = (Connection)stack.peek(); + + if(port != null) { + if (!port.isOutPort()) { + System.out.println("Error: Connection " + + connection.getName() + + " must be defined in the direction of the " + + " data flow!"); + System.out.println("Exit."); + System.exit(-1); + } + connection.setOriginPort(port); + } else { + undefinedReference("Connection", connection.getName(), + "element", resource.getName()); + } + + Process process = ((ProcessNetwork)stack.elementAt(0)). + getProcess(resource.getName()); + Channel channel = ((ProcessNetwork)stack.elementAt(0)). + getChannel(resource.getName()); + + if (process != null) { + connection.setOrigin(process); + } + else if (channel != null) { + connection.setOrigin(channel); + } + else { + undefinedReference("Connection", connection.getName(), + "element", resource.getName()); + } + } + + /** + * Process the start of a target tag in the XML. + * + * @param attributes attributes of the tag + * @return a Resource object + */ + public Resource processTarget(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + Resource resource = new Resource(name); + if (basename != null) resource.setBasename(basename); + return resource; + } + + /** + * Process the end of a target tag in the XML. + * + * @param stack + */ + public void processTarget(Stack stack) { + Port port = (Port)stack.pop(); + Resource resource = (Resource)stack.pop(); + Connection connection = (Connection)stack.peek(); + + if(port != null) { + if (!port.isInPort()) { + System.out.println("Error: Connection " + + connection.getName() + + " must be defined in the direction of the " + + " data flow!"); + System.out.println("Exit."); + System.exit(-1); + } + connection.setTargetPort(port); + } else { + undefinedReference("Connection", connection.getName(), + "element", resource.getName()); + } + + Process process = ((ProcessNetwork)stack.elementAt(0)). + getProcess(resource.getName()); + Channel channel = ((ProcessNetwork)stack.elementAt(0)). + getChannel(resource.getName()); + + if (process != null) { + connection.setTarget(process); + } + else if (channel != null) { + connection.setTarget(channel); + } + else { + undefinedReference("Connection", connection.getName(), + "element", resource.getName()); + } + } + + /** + * Process the start of a connection tag in the XML. + * + * @param attributes attributes of the tag + * @return a Connection object + */ + public Connection processConnection(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String basename = (String) attributes.getValue("basename"); + Connection connection = new Connection(name); + if (basename != null) connection.setBasename(basename); + return connection; + } + + /** + * Process the end of a connection tag in the XML. + * + * @param stack + */ + public void processConnection(Stack stack) { + Connection connection = (Connection) stack.pop(); + ProcessNetwork pn = (ProcessNetwork)stack.peek(); + pn.getConnectionList().add(connection); + } + + /** + * Process the start of a port tag in the XML. + * + * @param attributes attributes of the tag + * @return a Port object + */ + public Port processPort(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String type = (String) attributes.getValue("type"); + String basename = (String) attributes.getValue("basename"); + String range = (String) attributes.getValue("range"); + + Port port = null; + + if (type != null) { + if( type.equals("input") ) { + port = new Port(name, Port.INPORT); + } else if( type.equals("output") ) { + port = new Port(name, Port.OUTPORT); + } + } else { + port = new Port(name); + } + + if (basename != null) + port.setBasename(basename); + else + port.setBasename(name); + + if (range != null) port.setRange(range); + + return port; + } + + /** + * Process the end of a port tag in the XML. + * + * @param stack + */ + public void processPort(Stack stack) { + Port port = (Port)stack.pop(); + Resource resource = (Resource)stack.peek(); + + //check if this is a port or just a port reference + if (!(stack.elementAt(stack.size() - 2) instanceof Connection)) { + port.setResource(resource); + resource.getPortList().add(port); + } + //port reference + else { + Process process = ((ProcessNetwork)stack.elementAt(0)). + getProcess(resource.getName()); + Channel channel = ((ProcessNetwork)stack.elementAt(0)). + getChannel(resource.getName()); + + Resource refResource = null; + + if (process != null) { + refResource = process; + } + else if (channel != null) { + refResource = channel; + } + else { + undefinedReference("Connection", + ((Connection)stack.peek()).getName(), + "element", resource.getName()); + } + + Port refPort = refResource.getPort(port.getName()); + + if (refPort == null) { + undefinedReference("Connection", + ((Connection)stack.peek()).getName(), + "port", port.getName()); + } + + // Put real port back on the stack. Will be popped in + // processOrigin and processTarget respectively + stack.push(refPort); + + } + } + + /** + * Process the start of a channel tag in the XML. + * + * @param attributes attributes of the tag + * @return a Channel object + */ + public Channel processChannel(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String size = (String) attributes.getValue("size"); + String type = (String) attributes.getValue("type"); + String tSize = (String) attributes.getValue("tokensize"); + Channel channel = new Channel(name); + channel.setSize(new Integer(size)); + channel.setTokenSize(new Integer(tSize)); + channel.setType(type); + return channel; + } + + /** + * Process the end of a channel tag in the XML. + * + * @param stack + */ + public void processChannel(Stack stack) { + Channel channel = (Channel) stack.pop(); + ProcessNetwork r = (ProcessNetwork)stack.peek(); + r.getChannelList().add(channel); + } + + /** + * Process the end of a configuration tag in the XML. + * + * @param stack + */ + public void processConfiguration(Stack stack) { + Configuration configuration = (Configuration) stack.pop(); + Resource r = (Resource) stack.peek(); + configuration.setParentResource(r); + r.getCfgList().add(configuration); + } + + /** + * Process the start of a configuration tag in the XML. + * + * @param attributes attributes of the tag + * @return a Configuration object + */ + public Configuration processConfiguration(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Configuration code = new Configuration(name); + code.setName(name); + code.setValue(value); + return code; + } + + /** + * Process the end of a profiling tag in the XML. + * + * @param stack + */ + public void processProfiling(Stack stack) { + ProfilingConfiguration configuration = ( ProfilingConfiguration) stack.pop(); + Resource r = (Resource) stack.peek(); + configuration.setParentResource(r); + r.getProfilingList().add(configuration); + } + + /** + * Process the start of a profiling tag in the XML. + * + * @param attributes attributes of the tag + * @return a profiling object + */ + public Configuration processProfiling(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + ProfilingConfiguration code = new ProfilingConfiguration(name); + code.setName(name); + code.setValue(value); + return code; + } + + /** + * Process the start of a channel source tag in the XML. + * + * @param attributes attributes of the tag + * @return a SourceCode object + */ + public SourceCode processSource(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String type = (String) attributes.getValue("type"); + String l = (String) attributes.getValue("location"); + SourceCode code = new SourceCode(name); + code.setType(type); + code.setLocality(l); + return code; + } + + /** + * Process the end of a source tag in the XML. + * + * @param stack + */ + public void processSource(Stack stack) { + SourceCode source = (SourceCode) stack.pop(); + Process process = (Process) stack.peek(); + source.setProcess(process); + process.getSrcList().add(source); + } + + /** + * Process the start of a variable tag in the XML. + * + * @param attributes attributes of the tag + * @return a Variable object + */ + public Variable processVariable(Attributes attributes) { + String name = (String) attributes.getValue("name"); + String value = (String) attributes.getValue("value"); + Variable variable = null; + variable = new Variable(name); + variable.setValue(Integer.parseInt(value)); + return variable; + } + + /** + * Process the end of a variable tag in the XML. + * + * @param stack + */ + public void processVariable(Stack stack) { + Variable variable = (Variable) stack.pop(); + ProcessNetwork pn = (ProcessNetwork)stack.peek(); + variable.setParentResource(pn); + pn.getVarList().add(variable); + } + + /** + * Write an error message and terminate program in case an + * processnetwork element is referenced which does not exist. + * + * @param elementType type of processnetwork element in which the error + * occurred + * @param elementName name of processnetwork element in which the error + * occurred + * @param referenceType type of referenced processnetwork element + * @param referenceName name of referenced processnetwork element + */ + protected void undefinedReference(String elementType, + String elementName, + String referenceType, + String referenceName) { + System.out.println("Error: " + elementType + " " + elementName + + " references " + referenceType + " " + referenceName + + " that has not been declared."); + System.out.println("Exit."); + System.exit(-1); + } +} diff --git a/dol/src/dol/parser/xml/pnschema/package.html b/dol/src/dol/parser/xml/pnschema/package.html new file mode 100644 index 0000000..97ca537 --- /dev/null +++ b/dol/src/dol/parser/xml/pnschema/package.html @@ -0,0 +1,21 @@ + + + + + + +Process network XML schema parser. +This package parses the process network XML and stores the result in internal data model for the process network. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/util/ApplicationGenerator.java b/dol/src/dol/util/ApplicationGenerator.java new file mode 100644 index 0000000..a193614 --- /dev/null +++ b/dol/src/dol/util/ApplicationGenerator.java @@ -0,0 +1,914 @@ +/* $Id: ApplicationGenerator.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Random; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.WritePath; +import dol.datamodel.mapping.CommunicationBinding; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.mapping.Schedule; +import dol.datamodel.mapping.ScheduleEntry; +import dol.datamodel.mapping.SchedulingPolicy; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Connection; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.datamodel.pn.SourceCode; +import dol.visitor.xml.MapXmlVisitor; +import dol.visitor.xml.PNXmlVisitor; + +/** + * + */ +public class ApplicationGenerator { + /* + protected String _processes[] = + {"producer", "consumer"}; + + protected int _computation[] = + {1000, 1000}; + + protected int _communication[][] = + {{0, 4}, {-4, 0}}; + + protected String _binding[] = + {"tile_0.arm", "tile_0.arm"}; + + protected int _iterations = 1000; + */ + + /* + protected String _processes[] = + {"producer", "forwarder", "consumer"}; + + protected int _computation[] = + {1000, 1000, 1000}; + + protected int _communication[][] = + {{ 0, 4, 0}, + {-4, 0, 4}, + { 0, -4, 0}}; + + protected String _binding[] = + {"tile_0.arm", "tile_0.arm", "tile_0.arm"}; + + protected int _iterations = 10; + */ + + /* + protected String _processes[] = + {"stageA", "stageB", "stageC", "stageD", + "stageE", "stageF", "stageG", "stageH", + "stageI", "stageJ", "stageK", "stageL", + "stageM", "stageN", "stageO", "stageP"}; + + protected int _computation[] = + {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + + protected int _communication[][] = + {{ 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {-1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0, 1024}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1024, 0}}; + + + //protected String _binding[] = + // {"tile_0.arm", "tile_0.arm", "tile_1.arm", "tile_1.arm", + // "tile_2.arm", "tile_2.arm", "tile_3.arm", "tile_3.arm", + // "tile_4.arm", "tile_4.arm", "tile_5.arm", "tile_5.arm", + // "tile_6.arm", "tile_6.arm", "tile_7.arm", "tile_7.arm"}; + + //protected String _binding[] = + // {"tile_0.arm", "tile_1.arm", "tile_2.arm", "tile_3.arm", + // "tile_4.arm", "tile_5.arm", "tile_6.arm", "tile_7.arm", + // "tile_0.arm", "tile_1.arm", "tile_2.arm", "tile_3.arm", + // "tile_4.arm", "tile_5.arm", "tile_6.arm", "tile_7.arm"}; + + protected String _binding[] = + {"tile_0.arm", "tile_1.arm", "tile_2.arm", "tile_3.arm", + "tile_4.arm", "tile_5.arm", "tile_6.arm", "tile_7.arm", + "tile_7.arm", "tile_6.arm", "tile_5.arm", "tile_4.arm", + "tile_3.arm", "tile_2.arm", "tile_1.arm", "tile_0.arm"}; + + protected int _iterations = 10; + */ + + protected String _processes[] = + {"splitAA", "splitBA", "splitBB", + "pipeAA", "pipeBA", "pipeCA", "pipeDA", + "pipeEA", "pipeFA", "pipeAB", "pipeBB", + "pipeCB", "pipeDB", "pipeEB", "pipeFB", + "mergeBA", "mergeBB", "mergeAA"}; + + protected int _armComputation[] = + {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + protected int _magicComputation[] = + {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + + + protected int _communication[][] = + {{ 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -4, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -4, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, -4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0}, + { 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, + { 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0}, + { 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0}, + { 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0}, + { 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}, + { 0, 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}, + { 0, 0, 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}, + { 0, 0, 0, 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0}, + { 0, 0, 0, 0, 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, -4, 0, 0, 0, 0, 0, 0, 0, 4, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, -4, -4, -4, 0, 0, 0, 0, 0, 4}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4, -4, -4, 0, 0, 4}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4, -4, 0}}; + + + //working + protected String _binding[] = + {"tile_0.arm", "tile_1.arm", "tile_4.arm", + "tile_1.arm", "tile_2.arm", "tile_3.arm", "tile_4.arm", + "tile_5.arm", "tile_6.arm", "tile_1.arm", "tile_2.arm", + "tile_3.arm", "tile_4.arm", "tile_5.arm", "tile_6.arm", + "tile_3.arm", "tile_7.arm", "tile_0.arm"}; + + /* + protected String _binding[] = + {"tile_0.arm", "tile_1.arm", "tile_3.arm", + "tile_1.arm", "tile_1.arm", "tile_2.arm", "tile_2.arm", + "tile_3.arm", "tile_3.arm", "tile_4.arm", "tile_4.arm", + "tile_5.arm", "tile_5.arm", "tile_6.arm", "tile_6.arm", + "tile_7.arm", "tile_7.arm", "tile_0.arm"}; + + protected String _binding[] = + {"tile_0.arm", "tile_1.arm", "tile_7.arm", + "tile_1.arm", "tile_1.arm", "tile_2.arm", "tile_2.arm", + "tile_3.arm", "tile_3.arm", "tile_4.arm", "tile_4.arm", + "tile_5.arm", "tile_5.arm", "tile_6.arm", "tile_6.arm", + "tile_7.arm", "tile_3.arm", "tile_0.arm"}; + */ + + //not working + /* + protected String _binding[] = + {"tile_0.arm", "tile_1.arm", "tile_2.arm", "tile_3.arm", + "tile_4.arm", "tile_5.arm", "tile_6.arm", "tile_7.arm", + "tile_7.arm", "tile_6.arm", "tile_5.arm", "tile_4.arm", + "tile_3.arm", "tile_2.arm", "tile_1.arm", "tile_0.arm", + "tile_0.arm", "tile_0.arm"}; + */ + + protected int _iterations = 10; + + public String getStringEncoding(int i, int total) { + if (total > 26 * 26) { + System.out.println("\"total\" needs to be less than " + + 26 * 26 + "."); + return ""; + } else if (total <= 26) { + return Character.toString((char)('A' + (i % 26))); + } + return Character.toString((char)('A' + (i / 26))) + + Character.toString((char)('A' + (i % 26))); + } + + /** + * + * @param stages + * @param communication + */ + public void generateIndependentTasks(int numberOfTasks) { + _processes = new String[numberOfTasks]; + _armComputation = new int[numberOfTasks]; + _magicComputation = new int[numberOfTasks]; + _communication = new int[numberOfTasks][numberOfTasks]; + _binding = new String[numberOfTasks]; + _iterations = 1000; + + for (int i = 0; i < numberOfTasks; i++) { + _processes[i] = ((i < numberOfTasks / 2) ? "armProcess" + : "magicProcess") + + getStringEncoding(i, numberOfTasks); + System.out.println(_processes[i]); + _armComputation[i] = + (i < numberOfTasks / 2) ? 100000000 : 200000000; + _magicComputation[i] = + (i < numberOfTasks / 2) ? 200000000 : 100000000; + _binding[i] = (i < numberOfTasks / 2) ? "tile_0.arm" + : "tile_0.magic"; + } + + //no communication + for (int i = 0; i < numberOfTasks; i++) { + for (int j = 0; j < numberOfTasks; j++) { + _communication[i][j] = 0; + } + } + } + + /** + * + * @param pipelines + * @param stages + * @param communication + */ + public void generateIndpendentPipelines(int pipelines, int stages, int communication) { + Random random = new Random(); + _processes = new String[stages * pipelines]; + _armComputation = new int[stages * pipelines]; + _magicComputation = new int[stages * pipelines]; + _communication = new int[stages * pipelines][stages * pipelines]; + _binding = new String[stages * pipelines]; + _iterations = 1000; + + for (int i = 0; i < pipelines; i++) { + for (int j = 0; j < stages; j++) { + _processes[i * stages + j] = + ((i < pipelines / 2) ? "arm" : "magic") + + "stage" + + getStringEncoding(i, pipelines) + "x" + + getStringEncoding(j, stages); + System.out.println(_processes[i * stages + j]); + _armComputation[i * stages + j] = + 0 * random.nextInt(10000000) + + ((i < pipelines / 2) ? 100000000 : 200000000); + _magicComputation[i * stages + j] = + 0 * random.nextInt(10000000) + + ((i < pipelines / 2) ? 200000000 : 100000000); + _binding[i * stages + j] = "tile_0.arm"; + } + } + + for (int i = 0; i < pipelines; i++) { + for (int j = 0; j < stages; j++) { + for (int k = 0; k < pipelines; k++) { + for (int l = 0; l < stages; l++) { + _communication[i * stages + j][k * stages + l] = 0; + if (i == k && l == j + 1) { + _communication[i * stages + j][k * stages + l] = communication; + } else if (i == k && j == l + 1) { + _communication[i * stages + j][k * stages + l] = -communication; + } + } + } + } + } + } + + + /** + * + * @param pipelines + * @param stages + * @param communication + */ + public void generateIndpendentPipelines2(int pipelines, int stages, int communication) { + Random random = new Random(); + _processes = new String[stages * pipelines]; + _armComputation = new int[stages * pipelines]; + _magicComputation = new int[stages * pipelines]; + _communication = new int[stages * pipelines][stages * pipelines]; + _binding = new String[stages * pipelines]; + _iterations = 1000; + + for (int i = 0; i < pipelines; i++) { + for (int j = 0; j < stages; j++) { + _processes[i * stages + j] = + ((j < stages / 2) ? "arm" : "magic") + + "stage" + + getStringEncoding(i, pipelines) + "x" + + getStringEncoding(j, stages); + System.out.println(_processes[i * stages + j]); + _armComputation[i * stages + j] = + 0 * random.nextInt(10000000) + + ((j < stages / 2) ? 100000000 : 200000000); + _magicComputation[i * stages + j] = + 0 * random.nextInt(10000000) + + ((j < stages / 2) ? 200000000 : 100000000); + _binding[i * stages + j] = "tile_0.arm"; + } + } + + for (int i = 0; i < pipelines; i++) { + for (int j = 0; j < stages; j++) { + for (int k = 0; k < pipelines; k++) { + for (int l = 0; l < stages; l++) { + _communication[i * stages + j][k * stages + l] = 0; + if (i == k && l == j + 1) { + _communication[i * stages + j][k * stages + l] = communication; + } else if (i == k && j == l + 1) { + _communication[i * stages + j][k * stages + l] = -communication; + } + } + } + } + } + } + + + /** + * + * @param stages + * @param communication + */ + public void generatePipeline(int stages, int communication) { + _processes = new String[stages]; + _armComputation = new int[stages]; + _magicComputation = new int[stages]; + _communication = new int[stages][stages]; + _binding = new String[stages]; + //_iterations = 1000; + _iterations = 1000; + + for (int i = 0; i < stages; i++) { + _processes[i] = ((i % 8 < 4) ? "arm" : "magic") + + "stage" + getStringEncoding(i, stages); + System.out.println(_processes[i]); + _armComputation[i] = + (i % 8 < 4) ? 100 : 200; + _magicComputation[i] = + (i % 8 < 4) ? 200 : 100; + _binding[i] = "tile_0.arm"; + } + + for (int i = 0; i < stages; i++) { + for (int j = 0; j < stages; j++) { + _communication[i][j] = 0; + if (j == i + 1) { + _communication[i][j] = communication; + } else if (i == j + 1) { + _communication[i][j] = -communication; + } + } + } + } + + + /** + * + * @param pn + */ + public void generateProcessNetwork(ProcessNetwork pn) { + System.out.print("Generate process network. "); + int numberOfProcesses = 0; + int numberOfChannels = 0; + + for (int j = 0; j < _processes.length; j++) { + String processName = _processes[j]; + Process p = new Process(processName); + SourceCode srcCode = new SourceCode(processName); + srcCode.setLocality(processName + ".c"); + srcCode.setType("c"); + p.getSrcList().add(srcCode); + pn.getProcessList().add(p); + numberOfProcesses++; + for (int i = 0; i < 8; i++) { + ProfilingConfiguration c = new ProfilingConfiguration( + "BCED tile_" + i + ".arm"); + c.setValue(Integer.toString(_armComputation[j])); + p.getProfilingList().add(c); + c = new ProfilingConfiguration( + "BCED tile_" + i + ".magic"); + c.setValue(Integer.toString(_magicComputation[j])); + p.getProfilingList().add(c); + } + } + + //check whether communication matrix is symmetric + if (_communication[0].length != numberOfProcesses) { + System.out.println(); + System.out.println("Error: Size of communication matrix (" + + _communication[0].length + " columns) must match " + + "number of processes (" + numberOfProcesses + ")."); + System.exit(-1); + } + for (int row = 0; row < numberOfProcesses; row++) { + for (int col = 0; col < numberOfProcesses; col++) { + if (row == col && _communication[row][col] != 0) { + System.out.println(); + System.out.println("Error: No self-channels allowed. " + + "Problem for process " + row + " (" + + _processes[row] + ")."); + System.exit(-1); + } + if (_communication[row][col] != + -_communication[col][row]) { + System.out.println(); + System.out.println("Error: Communication matrix " + + "needs to be symmetric. Mismatch for row " + + row + " column " + col + "."); + System.exit(-1); + } + } + } + + for (int row = 0; row < numberOfProcesses; row++) { + for (int col = row; col < numberOfProcesses; col++) { + if (_communication[row][col] == 0) { + continue; + } + int src = row; + int dst = col; + + if (_communication[row][col] < 0) { + src = col; + dst = row; + } + + Channel c = new Channel("channel" + _processes[src] + + _processes[dst]); + c.setSize(Math.abs(2 * _communication[src][dst])); + c.setType("fifo"); + ProfilingConfiguration p = new ProfilingConfiguration( + "TotalReadData"); + p.setValue(Integer.toString(_communication[row][col])); + c.getProfilingList().add(p); + pn.getChannelList().add(c); + numberOfChannels++; + + Port channelPort0 = new Port("0", Port.INPORT); + Port channelPort1 = new Port("1", Port.OUTPORT); + c.getPortList().add(channelPort0); + c.getPortList().add(channelPort1); + + Connection c1 = new Connection( + "c1" + _processes[src] + _processes[dst]); + Connection c2 = new Connection( + "c2" + _processes[src] + _processes[dst]); + pn.getConnectionList().add(c1); + pn.getConnectionList().add(c2); + + Port p0 = new Port(Integer.toString(pn.getProcess( + _processes[src]).getPortList().size()), + Port.OUTPORT); + Port p1 = new Port(Integer.toString(pn.getProcess( + _processes[dst]).getPortList().size()), + Port.INPORT); + pn.getProcess(_processes[src]).getPortList().add(p0); + pn.getProcess(_processes[dst]).getPortList().add(p1); + + c1.setOriginPort(p0); + c1.setTargetPort(channelPort0); + c1.setOrigin(pn.getProcess(_processes[src])); + c1.setTarget(c); + p0.setPeerResource(c); + p0.setPeerPort(channelPort0); + channelPort0.setPeerResource(pn.getProcess( + _processes[src])); + channelPort0.setPeerPort(p0); + + c2.setOriginPort(channelPort1); + c2.setTargetPort(p1); + c2.setOrigin(c); + c2.setTarget(pn.getProcess(_processes[dst])); + channelPort1.setPeerResource(pn.getProcess( + _processes[dst])); + channelPort1.setPeerPort(p1); + p1.setPeerResource(c); + p1.setPeerPort(channelPort1); + + c.setOrigin(pn.getProcess(_processes[src])); + c.setTarget(pn.getProcess(_processes[dst])); + } + } + + System.out.println("Finished."); + } + + /** + * + * @param pn + */ + public void generateProcessNetworkXml(ProcessNetwork pn, + String filename) { + System.out.print("Generate process network XML. "); + StringBuffer buffer = new StringBuffer(); + pn.accept(new PNXmlVisitor(buffer)); + try { + java.io.BufferedWriter writer = + new java.io.BufferedWriter( + new java.io.FileWriter(filename)); + writer.write(buffer.toString()); + writer.close(); + } catch (java.io.IOException e) { + System.out.println(); + System.out.println("Error: Could not write XML file."); + System.exit(-1); + } + System.out.println("Finished."); + } + + /** + * + * @param pn + * @param path + */ + public void generateCode(ProcessNetwork pn, String path) { + System.out.print("Generate source code of processes. "); + + String newline = System.getProperty("line.separator"); + int maxProcessNameLength = 0; + for (String processName : _processes) { + maxProcessNameLength = Math.max(maxProcessNameLength, + processName.length()); + } + + String global = ""; + global += "#ifndef GLOBAL_H" + newline; + global += "#define GLOBAL_H" + newline; + global += newline; + + global += "#define ITERATIONS " + _iterations + newline; + global += newline; + + for (int i = 0; i < _processes.length; i++) { + global += "#define LOOPS_ARM_" + _processes[i].toUpperCase() + + " " + _armComputation[i] + newline; + global += "#define LOOPS_MAGIC_" + _processes[i].toUpperCase() + + " " + _magicComputation[i] + newline; + } + + for (int row = 0; row < _communication[0].length; row++) { + for (int col = row; col < _communication[0].length; col++) { + if (_communication[row][col] == 0) { + continue; + } + int src = row; + int dst = col; + + if (_communication[row][col] < 0) { + src = col; + dst = row; + } + + global += "#define TOKEN_SIZE_" + + _processes[src].toUpperCase() + "_" + + _processes[dst].toUpperCase() + " " + + _communication[src][dst] + newline; + } + } + global += newline; + global += "#endif" + newline; + + try { + BufferedWriter out = new BufferedWriter( + new FileWriter(path + "global.h")); + out.write(global); + out.close(); + } catch (Exception e) { + System.out.println(); + System.out.println("Error: While writing global.h file."); + System.exit(-1); + } + + + for (Process p : pn.getProcessList()) { + String state = p.getName().substring(0, 1).toUpperCase() + + p.getName().substring(1) + "_State"; + + String h = ""; + h += "#ifndef " + p.getName().toUpperCase() + "_H" + newline; + h += "#define " + p.getName().toUpperCase() + "_H" + newline; + h += newline; + h += "#include " + newline; + h += newline; + for (Port port : p.getPortList()) { + h += "#define PORT_" + p.getName().toUpperCase() + "_" + + (port.isInPort() ? "IN_" : "OUT_" ) + + port.getName() + " " + port.getName() + newline; + } + h += newline; + h += "typedef struct {" + newline; + h += " int iterations;" + newline; + h += "} " + state + ";" + newline; + h += newline; + h += "void " + p.getName() + "_init(DOLProcess *p);" + + newline; + h += "int " + p.getName() + "_fire(DOLProcess *p);" + newline; + h += newline; + h += "#endif" + newline; + + String c = ""; + String printPrefix = String.format(" printf(\"[%-" + + maxProcessNameLength + "s] ", p.getName()); + c += "#include " + newline; + c += newline; + c += "#include \"" + p.getName() + ".h\"" + newline; + c += "#include \"global.h\"" + newline; + c += newline; + c += "void " + p.getName() + "_init(DOLProcess *p) {" + + newline; + c += " " + state + " *state = (" + state + "*)p->local;" + + newline; + c += " state->iterations = 0;" + newline; + c += "}" + newline; + c += newline; + c += "int " + p.getName() + "_fire(DOLProcess *p) {" + + newline; + + int processIndex = getProcessIndex(p.getName()); + int maxBuffer = 0; + for (int i = 0; i < _communication[processIndex].length; i++) { + maxBuffer = Math.max(maxBuffer, + Math.abs(_communication[processIndex][i])); + } + c += " int buffer[" + ((maxBuffer + (4 - 1)) / 4) + "];" + newline; + c += " int i;" + newline; + c += " " + state + " *state = (" + state + "*)p->local;"; + c += newline + newline; + + for (Port port : p.getPortList()) { + if (port.isInPort()) { + int originIndex = getProcessIndex( + ((Channel)port.getPeerResource()).getOrigin() + .getName()); + String tokenSize = "TOKEN_SIZE_" + + _processes[originIndex].toUpperCase() + "_" + + _processes[processIndex].toUpperCase(); + c += printPrefix + "read %d bytes from " + + _processes[originIndex] + ".\\n\", " + tokenSize + + ");" + newline; + c += " DOL_read((void*)" + + "PORT_" + p.getName().toUpperCase() + "_IN_" + + port.getName() + ", buffer, " + tokenSize + + ", p);" + newline; + } + } + c += newline; + //c += " printf(\"[%-20s ] iteration %d/n\", \"" + // + p.getName() + "\", state->iterations);" + newline; + c += printPrefix + "iteration %d.\\n\" , " + + "state->iterations);" + newline; + c += newline; + c += "#ifdef __arm__" + newline; + c += " for (i = 0; i < LOOPS_ARM_" + p.getName().toUpperCase() + + "; i++) { ; }" + newline; + c += "#else" + newline; + c += " for (i = 0; i < LOOPS_MAGIC_" + p.getName().toUpperCase() + + "; i++) { ; }" + newline; + c += "#endif" + newline; + for (Port port : p.getPortList()) { + if (port.isOutPort()) { + int targetIndex = getProcessIndex( + ((Channel)port.getPeerResource()).getTarget() + .getName()); + String tokenSize = "TOKEN_SIZE_" + + _processes[processIndex].toUpperCase() + "_" + + _processes[targetIndex].toUpperCase(); + c += printPrefix + "write %d bytes to " + + _processes[targetIndex] + ".\\n\", " + tokenSize + + ");" + newline; + c += " DOL_write((void*)" + + "PORT_" + p.getName().toUpperCase() + "_OUT_" + + port.getName() + ", buffer, "+ tokenSize + + ", p);" + newline; + } + } + c += newline; + c += " state->iterations++;" + newline; + c += " if (state->iterations >= ITERATIONS) {" + + newline; + c += " DOL_detach(p);" + newline; + c += " return -1;" + newline; + c += " }" + newline; + c += newline; + c += " return 0;" + newline; + c += "}" + newline; + + try { + BufferedWriter out = new BufferedWriter( + new FileWriter(path + p.getName() + ".h")); + out.write(h); + out.close(); + out = new BufferedWriter( + new FileWriter(path + p.getName() + ".c")); + out.write(c); + out.close(); + } catch (Exception e) { + System.out.println(); + System.out.println("Error: While writing source files."); + System.exit(-1); + } + } + System.out.println("Finished."); + } + + + /** + * + * @param map + * @param pn + */ + public void generateMapping(ProcessNetwork pn, Mapping map) { + System.out.print("Generate mapping. "); + map.setPN(pn); + + //binding of processes + Vector processors = new Vector(); + for (int i = 0; i < _binding.length; i++) { + String processName = _processes[i]; + String processorName = _binding[i]; + Processor processor = null; + + boolean processorExists = false; + for (Processor proc : processors) { + if (proc.getName().equals(processorName)) { + processor = proc; + processorExists = true; + break; + } + } + + if (!processorExists) { + processor = new Processor(processorName); + processors.add(processor); + } + + ComputationBinding compBinding = new ComputationBinding( + processName + "binding"); + compBinding.setProcess(pn.getProcess(processName)); + compBinding.setProcessor(processor); + processor.getProcessList().add(pn.getProcess(processName)); + map.getCompBindList().add(compBinding); + } + + //schedules + for (Processor p : processors) { + Schedule s = new Schedule(p.getName() + "schedule"); + s.setSchedPolicy(SchedulingPolicy.FIFO); + s.setResource(p); + for (Process process : p.getProcessList()) { + ScheduleEntry entry = new ScheduleEntry( + process.getName()); + entry.setConsumer(process); + s.getEntryList().add(entry); + } + map.getScheduleList().add(s); + } + + //binding of channels + for (Channel c : pn.getChannelList()) { + String originProcess = c.getOrigin().getName(); + String targetProcess = c.getTarget().getName(); + + String originBinding = _binding[getProcessIndex( + originProcess)]; + String targetBinding = _binding[getProcessIndex( + targetProcess)]; + + Pattern pattern0 = Pattern.compile( + "tile_(\\d)\\.(arm)|tile_(\\d)\\.(magic)"); + Matcher matcher0 = pattern0.matcher(originBinding); + Pattern pattern1 = Pattern.compile( + "tile_(\\d)\\.(arm)|tile_(\\d)\\.(magic)"); + Matcher matcher1 = pattern1.matcher(targetBinding); + + if (!matcher0.matches() || !matcher1.matches()) { + System.out.println("binding needs to comply to the" + + " following regular expression: " + + "tile_(\\d)\\.(arm)|tile_(\\d)\\.(magic)"); + System.exit(-1); + } + + int srcIndex = Integer.valueOf(matcher0.group(1)); + int dstIndex = Integer.valueOf(matcher1.group(1)); + String srcProcessor = matcher0.group(2); + String dstProcessor = matcher1.group(2); + + String writePath = ""; + String readPath = ""; + + if (srcIndex == dstIndex) { + if (srcProcessor.equals("arm")) { + writePath = "tile_" + srcIndex + ".rdmtodxm"; + } else { + writePath = "tile_" + srcIndex + ".ddmtodxm"; + } + } else { + if (srcProcessor.equals("arm")) { + writePath = "tile_" + srcIndex + ".rdmtodnp_" + + dstIndex; + } else { + writePath = "tile_" + srcIndex + ".ddmtodnp_" + + dstIndex; + } + } + + if (dstProcessor.equals("arm")) { + readPath = "tile_" + dstIndex + ".rdmfromdxm"; + } else { + readPath = "tile_" + dstIndex + ".ddmfromdxm"; + } + + CommunicationBinding commBinding = new CommunicationBinding( + c.getName() + "binding"); + commBinding.setChannel(c); + commBinding.setWritePath(new WritePath(writePath)); + commBinding.setReadPath(new ReadPath(readPath)); + map.getCommBindList().add(commBinding); + } + System.out.println("Finished."); + } + + /** + * + * @param processName + * @return + */ + protected int getProcessIndex(String processName) { + for (int i = 0; i < _processes.length; i++) { + if (_processes[i].equals(processName)) { + return i; + } + } + System.out.println("Error: Could not find process " + + processName + "."); + System.exit(-1); + return 0; + } + + /** + * + * @param map + */ + public void generateMappingXml(Mapping map, String filename) { + System.out.print("Generate mapping XML. "); + StringBuffer buffer = new StringBuffer(); + map.accept(new MapXmlVisitor(buffer)); + try { + java.io.BufferedWriter writer = + new java.io.BufferedWriter( + new java.io.FileWriter(filename)); + writer.write(buffer.toString()); + writer.close(); + } catch (java.io.IOException e) { + System.out.println("Could not write XML file."); + System.exit(-1); + } + System.out.println("Finished."); + } + + /** + * + * @param args + */ + public static void main(String args[]) { + /* + Pattern pattern = Pattern.compile("tile_(\\d)\\."); + Matcher matcher = pattern.matcher("tile_3."); + matcher.matches(); + System.out.println(matcher.group(1)); + */ + + String appName = "exampleAuto"; + String path = System.getProperty("user.dir") + "/" + appName + "/"; + File dir = new File(path); + dir.mkdirs(); + dir = new File(path + "src/"); + dir.mkdirs(); + + System.out.println("Generate application \"" + appName + "\"" + + " in directory \"" + path + "\"."); + ApplicationGenerator appGen = new ApplicationGenerator(); + //appGen.generateIndpendentPipelines2(16, 4, 10000000); + appGen.generatePipeline(64, 10); + //appGen.generateIndependentTasks(64); + ProcessNetwork pn = new ProcessNetwork(appName); + appGen.generateProcessNetwork(pn); + appGen.generateProcessNetworkXml(pn, path + appName + ".xml"); + appGen.generateCode(pn, path + "src/"); + Mapping map = new Mapping("mapping"); + appGen.generateMapping(pn, map); + appGen.generateMappingXml(map, path + "mapping.xml"); + System.out.println("Done."); + } +} \ No newline at end of file diff --git a/dol/src/dol/util/CheckXMLs.java b/dol/src/dol/util/CheckXMLs.java new file mode 100644 index 0000000..71ff63f --- /dev/null +++ b/dol/src/dol/util/CheckXMLs.java @@ -0,0 +1,45 @@ +/* $Id: CheckXMLs.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.ProcessNetwork; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; +import dol.parser.xml.pnschema.PNXmlParser; + + +public class CheckXMLs { + public static void main(String args[]) { + + /* + String pnFile = "D:\\shapes\\pa\\tools\\exampleTest.xml"; + String archFile = "D:\\shapes\\pa\\tools\\rdt8.xml"; + String mapFile = "D:\\shapes\\pa\\tools\\mapping_2tiles.xml"; + */ + String pnFile = "processnetwork.xml"; + String archFile = "rdt8.xml"; + String mapFile = "mapping.xml"; + + if (args.length == 3) { + pnFile = args[0]; + archFile = args[1]; + mapFile = args[2]; + } + + System.out.println("Process network: " + pnFile); + System.out.println("Architecture: " + archFile); + System.out.println("Mapping: " + mapFile); + PNXmlParser parserPn = new PNXmlParser(); + ProcessNetwork pn = parserPn.doParse(pnFile); + + ArchiXmlParser parserArch = new ArchiXmlParser(); + Architecture arch = parserArch.doParse(archFile); + + MapXmlParser parserMap = new MapXmlParser(pn, arch); + Mapping mapping = parserMap.doParse(mapFile); + + mapping.getArch(); + System.out.println("XML files seem to be consistent."); + } +} \ No newline at end of file diff --git a/dol/src/dol/util/CodePrintStream.java b/dol/src/dol/util/CodePrintStream.java new file mode 100644 index 0000000..2a3a80b --- /dev/null +++ b/dol/src/dol/util/CodePrintStream.java @@ -0,0 +1,115 @@ +/* $Id: CodePrintStream.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * Class to print code that is correctly indented. + */ +public class CodePrintStream extends PrintStream { + + /** + * + */ + public CodePrintStream(OutputStream out) { + super(out); + } + + /** + * + */ + public CodePrintStream(OutputStream out, boolean autoFlush) { + super(out, autoFlush); + } + + /** + * + */ + public void print(String x) { + super.print(x); + } + + /** + * + */ + public void println(String x) { + super.println(x); + } + + /** + * + */ + public void printPrefix(String x) { + super.print(_prefix + x); + } + + /** + * + */ + public void printPrefix() { + super.print(_prefix); + } + + /** + * + */ + public void printPrefixln(String x) { + super.println(_prefix + x); + } + + /** + * + */ + public void printPrefixln() { + super.println(); + } + + /** + * Print an opening curly brace and increase the indentation. + */ + public void printLeftBracket() { + printPrefix(); + println("{"); + prefixInc(); + } + + /** + * Decrease the indentation and print a closing curly brace. + */ + public void printRightBracket() { + prefixDec(); + printPrefixln("}"); + } + + /** + * Decrement the indentation. + */ + public void prefixDec() { + if( _prefix.length() >= _offset.length() ) { + _prefix = _prefix.substring(_offset.length()); + } + } + + /** + * Increment the indentation. + */ + public void prefixInc() { + _prefix += _offset; + } + + /** + * + */ + public static void main(String[] args) { + CodePrintStream ps = new CodePrintStream(System.out); + ps.println("aa"); + ps.print("bb"); + } + + private static String _offset = " "; + private String _prefix = ""; +} + + + diff --git a/dol/src/dol/util/CodePrintString.java b/dol/src/dol/util/CodePrintString.java new file mode 100644 index 0000000..b7ab780 --- /dev/null +++ b/dol/src/dol/util/CodePrintString.java @@ -0,0 +1,129 @@ +/* $Id: CodePrintString.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +/** + * Class to print code that is correctly indented. + */ +public class CodePrintString { + + /** + * Default constructor. + * + * @param stringBuffer string buffer where the result is stored + */ + public CodePrintString(StringBuffer stringBuffer) { + _code = stringBuffer; + } + + /** + * Print a line without indentation. + */ + public void print(String string) { + _code.append(string); + } + + /** + * Add a line break to the current line. Results in an empty + * line if the current line is empty. + */ + public void println() { + _code.append(System.getProperty("line.separator")); + } + + /** + * Print a line without indentation and add a line break. + * @param string string to print + */ + public void println(String string) { + _code.append(string + System.getProperty("line.separator")); + } + + /** + * Print spaces according to current level of indentation. + */ + public void printPrefix() { + _code.append(_prefix); + } + + /** + * Print a line at the current level of indentation. + * @param string string to print + */ + public void printPrefix(String string) { + _code.append(_prefix + string); + } + + /** + * Print a line at the current level of indentation and a line break. + * @param string string to print + */ + public void printPrefixln(String string) { + _code.append(_prefix + string + + System.getProperty("line.separator")); + } + + /** + * Print the opening tag for the given XML tag name and increase + * the indentation. + * @param tagName name of the XML tag + */ + public void printOpeningTag(String tagName) { + printPrefix(); + print("<" + tagName); + prefixInc(); + } + + /** + * Decrease the indentation and print the closing tag for the given + * XML tag name. + * @param tagName name of the XML tag + */ + public void printClosingTag(String tagName) { + prefixDec(); + printPrefixln(""); + } + + /** + * Decrement the indentation. + */ + public void prefixDec() { + if( _prefix.length() >= _offset.length() ) { + _prefix = _prefix.substring(_offset.length()); + } + } + + /** + * Increment the indentation. + */ + public void prefixInc() { + _prefix += _offset; + } + + /** + * Get the formatted output string. + * @return output string + */ + public String toString() { + return _code.toString(); + } + + /** + * Test the CodePrintString implementation. + */ + public static void main(String[] args) { + StringBuffer buffer = new StringBuffer(); + CodePrintString ps = new CodePrintString(buffer); + ps.printOpeningTag("tag"); + ps.println(">"); + ps.printPrefixln(""); + ps.printClosingTag("tag"); + System.out.println(ps.toString()); + } + + protected static String _offset = " "; + protected String _prefix = ""; + protected StringBuffer _code = null; +} + + + diff --git a/dol/src/dol/util/Copier.java b/dol/src/dol/util/Copier.java new file mode 100644 index 0000000..cb230d7 --- /dev/null +++ b/dol/src/dol/util/Copier.java @@ -0,0 +1,155 @@ +/* $Id: Copier.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.jar.JarFile; + +/** + * Class to recursively copy a directory. + */ +public class Copier { + + String _destination; //target directory + + /** + * Copy a file. + * + * @param from source file + * @param to destination file + */ + public void copyFile(File from, File to) + throws IOException { + + FileChannel srcChannel = new FileInputStream(from).getChannel(); + FileChannel dstChannel = new FileOutputStream(to).getChannel(); + try { + dstChannel.transferFrom(srcChannel, 0, srcChannel.size()); + srcChannel.close(); + dstChannel.close(); + } + finally { + if (srcChannel != null) + srcChannel.close(); + + if (dstChannel != null) + dstChannel.close(); + } + } + + /** + * Method which defines what is done for each file found in the + * directory tree: It is copied to the corresponding target directory. + * + * @param filename file to process + * @param directory the subdirectory currently processed + */ + protected void processFile(String filename, String directory) + throws IOException { + File from = new File(filename); + File to = new File(_destination + directory + from.getName()); + copyFile(from, to); + //System.out.println("Copied " + from.getPath() + " to " + // + to.getPath()); + } + + /** + * Iterate through the given directory tree. For each file found in + * the file, the method + * {@link #processFile(java.lang.String, java.lang.String)} is called. + * + * @param path the path of the directory to be browsed + * @param currentDir the subdirectory currently explored + */ + protected void browseDirectoryTree(String path, String currentDir) + throws IOException { + File file = new File(path); + + if (!file.exists()) return; + if (!file.isDirectory()) return; + + //loop through files in directory + String[] files = file.list(); + + for (int k = 0; k < files.length; k++) { + File newfile = new File(file.getPath(), files[k]); + if (newfile.isFile()) + processFile(path + System.getProperty("file.separator") + + files[k], currentDir); + else if (newfile.isDirectory()) { + File newDirectory = new File(_destination + currentDir + + files[k]); + newDirectory.mkdirs(); + //System.out.println("Created directory " + // + newDirectory.getPath()); + browseDirectoryTree(file.getPath() + + System.getProperty("file.separator") + + files[k], currentDir + files[k] + + System.getProperty("file.separator")); + } + } + } + + /** + * Recursively copy a directory. + * + * @param source source directory + * @param destination target directory + */ + public void copy(File source, File destination) + throws IOException { + try { + //treat jar archives differently + if (source.toString().contains("dol.jar!")) { + String jarName = source.toString(); + jarName = jarName.substring(jarName.indexOf("file:") + 5, jarName.lastIndexOf("!")); + String sourceName = source.toString(); + sourceName = sourceName.substring( + sourceName.lastIndexOf("!") + 2); + sourceName = sourceName.replaceAll("\\\\", "/"); + + JarFile jar = new JarFile(jarName); + JarCopier copier = new JarCopier(); + copier.copy(jar, sourceName, destination.toString()); + return; + } + + _destination = destination.getPath(); + + if (!source.isDirectory()) + throw (new IOException("Source is not a directory.")); + + File destinationDir = new File(_destination); + destinationDir.mkdirs(); + + browseDirectoryTree(source.getPath(), + System.getProperty("file.separator")); + } + catch (IOException e) { + System.out.println("An error has occured while copying \"" + + source.getPath() + "\" to \"" + + destination.getPath() + "\":"); + System.out.println(e.getMessage()); + throw e; + } + } + + /** + * Test the implementation. + * + * @param args args[0] specifies the source directory, + * args[1] specifies the destination directory + */ + public static void main(String args[]) + throws Exception { + Copier copier = new Copier(); + if (args.length == 2) + copier.copy(new File(args[0]), new File(args[1])); + else + System.out.println("usage: java Copier " + + "sourceDirectory targetDirectory"); + } +} diff --git a/dol/src/dol/util/JarCopier.java b/dol/src/dol/util/JarCopier.java new file mode 100644 index 0000000..aeff404 --- /dev/null +++ b/dol/src/dol/util/JarCopier.java @@ -0,0 +1,114 @@ +/* $Id: JarCopier.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Class to copy files from a jar archive. + */ +public class JarCopier { + + /** + * Copy a file from the specified jar file to the specified + * destination file. The file path must be specified using forward + * slashes, for instance, META-INF/MANIFEST.MF. + * + * @param jar jar archive + * @param from file to extract from jar archive + * @param to file to copy the file to + */ + public void copyFile(JarFile jar, String from, String to) { + try { + InputStream in = jar.getInputStream(jar.getEntry(from)); + OutputStream out = new FileOutputStream(new File(to)); + int c; + while ((c = in.read()) != -1) { + out.write(c); + } + in.close(); + out.close(); + } + catch (IOException e) { + System.out.println("An error has occured while copying \"" + + from + "\" to \"" + to + "\":"); + System.out.println(e.getMessage()); + } + } + + /** + * Get all files located in the given path of the given jar archive. + * The path must be specified with forward slashes. + * + * @param jar jar archive + * @param path path to search for files + * @return vector of all files in the specified path + */ + public Vector getFilesInDirectory(JarFile jar, String path) { + Vector filelist = new Vector(); + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + String entry = entries.nextElement().toString(); + if (entry.startsWith(path)) { + filelist.add(entry); + } + } + return filelist; + } + + /** + * Recursively copy a directory from the specified jar archive. + * + * @param jar jar archive + * @param srcDir source directory + * @param destDir target directory + */ + public void copy(JarFile jar, String srcDir, String destDir) + throws IOException { + File directory = new File(destDir); + if (!directory.exists()) { + directory.mkdirs(); + } + for (String file : getFilesInDirectory(jar, srcDir)) { + String dir = file.substring(0, file.lastIndexOf("/")); + directory = new File(destDir + + System.getProperty("file.separator") + + dir.substring(srcDir.length())); + if (!directory.exists()) { + directory.mkdirs(); + } + if (!file.endsWith("/")) { + copyFile(jar, file, destDir + + System.getProperty("file.separator") + + file.substring(srcDir.length() + 1)); + } + } + } + + /** + * Copy the specified directory from the jar archive to the specified + * destination. + * + * @param args args[0] specifies the jar file to read from, + * args[1] specifies the source directory, + * args[2] specifies the destination directory + */ + public static void main(String args[]) + throws Exception { + JarCopier copier = new JarCopier(); + JarFile jar = new JarFile(args[0]); + if (args.length == 3) + copier.copy(jar, args[1], args[2]); + else + System.out.println("usage: java JarCopier " + + "jarFile sourceDirectory targetDirectory"); + } +} \ No newline at end of file diff --git a/dol/src/dol/util/SchemaLocation.java b/dol/src/dol/util/SchemaLocation.java new file mode 100644 index 0000000..de46cef --- /dev/null +++ b/dol/src/dol/util/SchemaLocation.java @@ -0,0 +1,137 @@ +/* $Id: SchemaLocation.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +/** + * Class to get the location of schemas. + */ +public class SchemaLocation { + + protected static final String PN_NAMESPACE = + "http://www.tik.ee.ethz.ch/~shapes/schema/PROCESSNETWORK"; + protected static final String PN_LOCATION = + "http://www.tik.ee.ethz.ch/~shapes/schema/processnetwork.xsd"; + protected static final String ARCH_NAMESPACE = + "http://www.tik.ee.ethz.ch/~shapes/schema/ARCHITECTURE"; + protected static final String ARCH_LOCATION = + "http://www.tik.ee.ethz.ch/~shapes/schema/architecture.xsd"; + protected static final String ARCH_NAMESPACE_OLD = + "http://www.tik.ee.ethz.ch/~shapes/schema/ARCHITECTURE_OLD"; + protected static final String MAP_NAMESPACE = + "http://www.tik.ee.ethz.ch/~shapes/schema/MAPPING"; + protected static final String MAP_LOCATION = + "http://www.tik.ee.ethz.ch/~shapes/schema/mapping.xsd"; + protected static final String MAP_NAMESPACE_OLD = + "http://www.tik.ee.ethz.ch/~shapes/schema/MAPPING_OLD"; + + /** singleton instance */ + protected final static SchemaLocation _schemaLocation = + new SchemaLocation(); + + /** + * Default constructor. + */ + public SchemaLocation() { + } + + /** + * Return the process network namespace. + * + * @return process network namespace + */ + public static String getProcessNetworkNamespace() { + return PN_NAMESPACE; + } + + /** + * Return the process network schema location. + * + * @return process network schema location + */ + public static String getProcessNetworkSchemaLocation() { + return PN_LOCATION; + } + + /** + * Return the architecture namespace. + * + * @return architecture namespace + */ + public static String getArchitectureNamespace() { + return ARCH_NAMESPACE; + } + + /** + * Return the architecture schema location. + * + * @return architecture schema location + */ + public static String getArchitectureSchemaLocation() { + return ARCH_LOCATION; + } + + /** + * Return the mapping namespace. + * + * @return mapping namespace + */ + public static String getMappingNamespace() { + return MAP_NAMESPACE; + } + + /** + * Return the mapping schema location. + * + * @return mapping schema location + */ + public static String getMappingSchemaLocation() { + return MAP_LOCATION; + } + + /** + * Return a string with the references to the external schema files. + * + * @return references to external schemas + */ + public static String getExternalSchemaLocation() { + String loc = PN_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/processnetwork.xsd"); + loc += " " + ARCH_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/architecture.xsd"); + loc += " " + ARCH_NAMESPACE_OLD + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/architecture_old.xsd"); + loc += " " + MAP_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/mapping.xsd"); + return loc; + } + + /** + * Return a string with the references to the internal schema files. + * + * @return references to internal schemas + */ + public static String getInternalSchemaLocation() { + String loc = PN_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/internal/processnetwork_internal.xsd"); + loc += " " + ARCH_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/internal/architecture_internal.xsd"); + loc += " " + ARCH_NAMESPACE_OLD + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/internal/architecture_old_internal.xsd"); + loc += " " + MAP_NAMESPACE + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/internal/mapping_internal.xsd"); + loc += " " + MAP_NAMESPACE_OLD + " "; + loc += _schemaLocation.getClass().getResource( + "/schema/internal/mapping_old_internal.xsd"); + return loc; + } +} + + + diff --git a/dol/src/dol/util/Sed.java b/dol/src/dol/util/Sed.java new file mode 100644 index 0000000..ae610f6 --- /dev/null +++ b/dol/src/dol/util/Sed.java @@ -0,0 +1,133 @@ +/* $Id: Sed.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Class to find and replace strings in a files of a directory. + * The intention is to get a behavior similar to UNIX's sed (stream + * editor). + */ +public class Sed { + + /** + * In the given file, search for occurences of the given search + * pattern and replace it by the given replacement. + * searchPattern, replacementPattern will be used in a call of + * String.replaceAll(searchPattern, replacementPattern). + * + * @param filename file to search + * @param searchPattern regular expression to search for + * @param replacementPattern replacement + */ + protected void readReplace(String filename, String searchPattern, + String replacementPattern) throws IOException { + String line; + StringBuffer buffer = new StringBuffer(); + FileInputStream fileInputStream = new FileInputStream(filename); + BufferedReader reader = new BufferedReader( + new InputStreamReader(fileInputStream)); + while((line = reader.readLine()) != null) { + String newline = line.replaceAll(searchPattern, replacementPattern); + /* + if (!newline.equals(line)) { + System.out.println("Found pattern in " + filename + + ". New line: " + newline); + } + */ + buffer.append(newline + "\n"); + } + reader.close(); + BufferedWriter out = new BufferedWriter(new FileWriter(filename)); + out.write(buffer.toString()); + out.close(); + } + + /** + * Iterate through the given directory tree. For each file found in + * the file, the method + * {@link #processFile(java.lang.String)} is called. + * + * @param path the path of the directory to be browsed + * @param currentDir the subdirectory currently explored + */ + protected void browseDirectoryTree(String path) + throws IOException { + File file = new File(path); + + if (!file.exists()) return; + if (!file.isDirectory()) return; + + //loop through files in directory + String[] files = file.list(); + + for (int k = 0; k < files.length; k++) { + File newfile = new File(file.getPath(), files[k]); + if (newfile.isFile()) + readReplace(path + System.getProperty("file.separator") + + files[k], _searchPattern, _replacementPattern); + else if (newfile.isDirectory()) { + browseDirectoryTree(file.getPath() + + System.getProperty("file.separator") + + files[k]); + } + } + } + + /** + * In the given file or in the files located in the given directory, + * search for occurences of the given search pattern and replace it + * by the given replacement. searchPattern, replacementPattern will + * be used in a call of + * String.replaceAll(searchPattern, replacementPattern). + * + * @param path file or path to search + * @param searchPattern regular expression to search for + * @param replacementPattern replacement + */ + public void sed(String path, String searchPattern, + String replacementPattern) throws IOException { + File file = new File(path); + _searchPattern = searchPattern; + _replacementPattern = replacementPattern; + if (!file.exists()) return; + if (file.isFile()) { + readReplace(path, searchPattern, replacementPattern); + } + else if (file.isDirectory()) { + browseDirectoryTree(path); + } + } + + /** + * Test the implementation. + * + * @param args args[0] file or directory to search + * args[1] regular expression to search for + * args[2] replacement + */ + public static void main(String args[]) { + if (args.length == 3) { + try { + new Sed().sed(args[0], args[1], args[2]); + } + catch (IOException e) { + System.out.println("Sed: An error occured:"); + System.out.println(e.getMessage()); + } + } + else { + System.out.println("usage: java Sed directory|file " + + "searchPattern replacementPattern"); + } + } + + String _searchPattern = ""; + String _replacementPattern = ""; +} diff --git a/dol/src/dol/util/package.html b/dol/src/dol/util/package.html new file mode 100644 index 0000000..bb9fea5 --- /dev/null +++ b/dol/src/dol/util/package.html @@ -0,0 +1,20 @@ + + + + + + +Collection of tools for file operations. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/ArchiVisitor.java b/dol/src/dol/visitor/ArchiVisitor.java new file mode 100644 index 0000000..b72c2d2 --- /dev/null +++ b/dol/src/dol/visitor/ArchiVisitor.java @@ -0,0 +1,47 @@ +/* $Id: ArchiVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor; + +import dol.datamodel.architecture.ArchiConnection; +import dol.datamodel.architecture.ArchiResource; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Configuration; +import dol.datamodel.architecture.HWChannel; +import dol.datamodel.architecture.Memory; +import dol.datamodel.architecture.Node; +import dol.datamodel.architecture.PortNode; +import dol.datamodel.architecture.Processor; +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.Variable; +import dol.datamodel.architecture.WritePath; +import dol.main.UserInterface; +import dol.util.CodePrintStream; + +/** + * This class is an abstract class for a visitor that is used to + * generate an Archietcture description. + */ +public abstract class ArchiVisitor implements Visitor { + + public ArchiVisitor() { + _ui = UserInterface.getInstance(); + } + + public void visitComponent(Architecture x) { } + public void visitComponent(ArchiResource x) { } + public void visitComponent(Processor x) { } + public void visitComponent(Memory x) { } + public void visitComponent(HWChannel x) { } + public void visitComponent(Configuration x) {} + public void visitComponent(Variable x) { } + public void visitComponent(Node x) { } + public void visitComponent(PortNode x) { } + public void visitComponent(ArchiConnection x) {} + public void visitComponent(ReadPath x) {} + public void visitComponent(WritePath x) {} + + /** + * Stream where the print output is sent to. + */ + protected CodePrintStream _printStream = null; + protected UserInterface _ui = null; +} diff --git a/dol/src/dol/visitor/MapVisitor.java b/dol/src/dol/visitor/MapVisitor.java new file mode 100644 index 0000000..b12e585 --- /dev/null +++ b/dol/src/dol/visitor/MapVisitor.java @@ -0,0 +1,41 @@ +/* $Id: MapVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor; + +import dol.datamodel.mapping.Binding; +import dol.datamodel.mapping.CommunicationBinding; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Configuration; +import dol.datamodel.mapping.MapResource; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.mapping.Schedule; +import dol.datamodel.mapping.ScheduleEntry; +import dol.datamodel.mapping.Variable; +import dol.main.UserInterface; +import dol.util.CodePrintStream; + +/** + * This class is an abstract class for a visitor that is used to + * generate a mapping description. + */ +public abstract class MapVisitor implements Visitor { + + public MapVisitor() { + _ui = UserInterface.getInstance(); + } + + public void visitComponent(Mapping x) { } + public void visitComponent(MapResource x) { } + public void visitComponent(Binding x) {} + public void visitComponent(ComputationBinding x) {} + public void visitComponent(CommunicationBinding x) {} + public void visitComponent(Schedule x) {} + public void visitComponent(ScheduleEntry x) {} + public void visitComponent(Variable x) {} + public void visitComponent(Configuration x) {} + + /** + * Stream where the print output is sent to. + */ + protected CodePrintStream _printStream = null; + protected UserInterface _ui = null; +} diff --git a/dol/src/dol/visitor/PNVisitor.java b/dol/src/dol/visitor/PNVisitor.java new file mode 100644 index 0000000..ec36dd3 --- /dev/null +++ b/dol/src/dol/visitor/PNVisitor.java @@ -0,0 +1,44 @@ +/* $Id: PNVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Configuration; +import dol.datamodel.pn.Connection; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.datamodel.pn.Resource; +import dol.datamodel.pn.SourceCode; +import dol.datamodel.pn.Variable; +import dol.main.UserInterface; +import dol.util.CodePrintStream; + +/** + * This class is an abstract class for a visitor that is used to + * generate a Process Network description. + */ +public abstract class PNVisitor implements Visitor { + + public PNVisitor() { + _ui = UserInterface.getInstance(); + } + + public void visitComponent(ProcessNetwork x) { } + public void visitComponent(Resource x) { } + public void visitComponent(Process x) { } + public void visitComponent(Variable x) { } + public void visitComponent(Channel x) { } + public void visitComponent(Connection x) { } + public void visitComponent(Configuration x) { } + public void visitComponent(ProfilingConfiguration x) { } + public void visitComponent(Port x) { } + public void visitComponent(SourceCode x) { } + + /** + * Stream where the print output is sent to. + */ + protected CodePrintStream _printStream = null; + protected UserInterface _ui = null; + protected String _delimiter = "/"; +} diff --git a/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterMakefileVisitor.java b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterMakefileVisitor.java new file mode 100644 index 0000000..feb3257 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterMakefileVisitor.java @@ -0,0 +1,63 @@ +/* $Id: PipeAndFilterMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.PipeAndFilter; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * + */ +public class PipeAndFilterMakefileVisitor extends PNVisitor { + + /** + * + */ + public PipeAndFilterMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("CXX = g++"); + ps.println("CXXFLAGS = -g -Wall"); + ps.println("COMPILE = ${CXX} ${CXXFLAGS} -c"); + ps.println("LINK = ${CXX} -lpthread"); + ps.println("LIB_INC = -Ilib -Iwrappers -Iprocesses"); + ps.println(); + ps.println("src := $(wildcard lib/*.cpp) " + + "$(wildcard wrappers/*.cpp) $(wildcard *.cpp)"); + ps.println("obj = $(src:.cpp=.o)"); + ps.println(); + ps.println("app : ${obj} ${src}"); + ps.println("\t${LINK} -o " + _name + " $(obj)"); + ps.println(); + ps.println("%.o :"); + ps.println("\t${COMPILE} -o $(*D)/$(*F).o $(*D)/$(*F).cpp $(LIB_INC)"); + ps.println(); + ps.println("clean :"); + ps.println("\trm ${obj}"); + } + catch (IOException e) { + System.out.println(" PipeAndFilter Makefile Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected String _name = "sc_application"; + +} + diff --git a/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterModuleVisitor.java b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterModuleVisitor.java new file mode 100644 index 0000000..04d9f57 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterModuleVisitor.java @@ -0,0 +1,311 @@ +/* $Id: PipeAndFilterModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.PipeAndFilter; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.Resource; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + */ +public class PipeAndFilterModuleVisitor extends PNVisitor { + + /** + * Constructor. + */ + public PipeAndFilterModuleVisitor(String dir) { + _dir = dir; + } + + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "sc_application.cpp"; + OutputStream file = new FileOutputStream(filename); + _code = new CodePrintStream(file); + + _code.println("#include "); + _code.println(); + + _code.printPrefixln("#include \"lib/Fifo.h\""); + _code.printPrefixln("#include \"lib/Scheduler.h\""); + _code.printPrefixln("#include \"lib/WindowedFifo.h\""); + _code.println(); + _code.printPrefixln("#include \"wrappers/wrappers.h\""); + _code.println(); + _code.printPrefixln("#include \"processnetwork.h\""); + _code.println(); + _code.printPrefixln("int main(void)"); + _code.printLeftBracket(); + + _code.printPrefixln("std::map *processes"); + _code.printPrefixln(" = new std::map();"); + _code.println(); + + _code.printPrefixln("Scheduler *scheduler = " + + "new Scheduler();"); + _code.println(); + + //instantiate channels + for (Channel c : x.getChannelList()) { + if (c.getType().equals("fifo")) { + printProcessNetwork("Fifo *" + c.getName() + + " = new Fifo(\"" + c.getName() + + "\", " + c.getSize() * c.getTokenSize() + + ");"); + } else if (c.getType().equals("wfifo")) { + printProcessNetwork("WindowedFifo *" + c.getName() + + " = new WindowedFifo(\"" + c.getName() + + "\", " + c.getSize() * c.getTokenSize() + + ");"); + } + } + printProcessNetwork(""); + + // instantiate processes and connect to channels + for (Process p : x.getProcessList()) { + printProcessNetwork(p.getBasename() + "_wrapper *" + + p.getName() + " = new " + + p.getBasename() + "_wrapper(\"" + + p.getName() + "\");"); + printProcessNetwork("processes->insert(std::pair" + + "(\"" + + p.getName() + "\", " + p.getName() + "));"); + printProcessNetwork(""); + } + + //build the network + for (Channel ch : x.getChannelList()) { + ch.accept(this); + } + + printProcessNetwork("}"); + + String headerfilename = _dir + _delimiter + + "processnetwork.h"; + OutputStream headerfile = new FileOutputStream( + headerfilename); + CodePrintStream processnetworkHeader = new + CodePrintStream(headerfile); + processnetworkHeader.printPrefixln( + "#ifndef PROCESSNETWORK_H"); + processnetworkHeader.printPrefixln( + "#define PROCESSNETWORK_H"); + processnetworkHeader.println(); + + for (int counter = 1; counter <= _processNetworkCounter; + counter++) { + _code.printPrefixln("processnetwork_part" + + String.format("%03d", counter) + "::create(" + + "processes);"); + processnetworkHeader.println("#include " + + "\"processnetwork_part" + + String.format("%03d", counter) + ".h\""); + } + _code.println(); + + processnetworkHeader.println(); + processnetworkHeader.printPrefixln("#endif"); + + + //register processes + _code.printPrefixln("std::map::iterator process_iterator;"); + _code.printPrefixln("for (process_iterator = " + + "processes->begin();"); + _code.printPrefixln(" process_iterator != " + + "processes->end();"); + _code.printPrefixln(" process_iterator++) {"); + _code.printPrefixln(" scheduler->registerProcess(" + + "static_cast"); + _code.printPrefixln(" ((*process_iterator).second));"); + _code.printPrefixln("}"); + _code.println(); + + //run scheduler + _code.printPrefixln("scheduler->run();"); + _code.println(); + + //clean up + _code.println(); + _code.printPrefixln("for (process_iterator = " + + "processes->begin();"); + _code.printPrefixln(" process_iterator != " + + "processes->end();"); + _code.printPrefixln(" process_iterator++) {"); + _code.printPrefixln(" delete static_cast" + + "((*process_iterator).second);"); + _code.printPrefixln("}"); + _code.println(); + //_code.printPrefixln("std::cout << \"End.\" << std::endl;"); + _code.printPrefixln("return 0;"); + _code.printRightBracket(); + } + catch (Exception e) { + System.out.println("PipeAndFilterModuleVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void createProcessNetworkHeader(int part) + throws IOException { + String partString = String.format("%03d", part); + String filename = _dir + _delimiter + "processnetwork_part" + + partString + ".h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream header = new CodePrintStream(file); + + header.println("#ifndef PROCESSNETWORK_PART" + + partString + "_H"); + header.println("#define PROCESSNETWORK_PART" + + partString + "_H"); + header.println(); + header.println("#include "); + header.println("#include "); + header.println(); + header.println("#include \"lib/ProcessWrapper.h\""); + header.println("#include \"lib/Fifo.h\""); + header.println("#include \"lib/WindowedFifo.h\""); + header.println(); + header.println("#include \"wrappers/wrappers.h\""); + header.println(); + header.println("class processnetwork_part" + partString + " {"); + header.println(" public:"); + header.println(" processnetwork_part" + + partString + "();"); + header.println(" virtual ~processnetwork_part" + + partString + "();"); + header.println(" static void create("); + header.println(" std::map *processes);"); + header.println("};"); + header.println(); + header.println("#endif"); + } + + + /** + * + */ + protected void printProcessNetwork(String nextLine) + throws IOException { + if (!_fileOpen || + (_numberOfLines >= MAX_NUMBER_OF_LINES && + !nextLine.contains("->insert") && + !nextLine.equals("") && + !nextLine.contains("Port(") && + !nextLine.contains(""))) { + + if (_fileOpen) { + _pn.printRightBracket(); + } + + _processNetworkCounter++; + _numberOfLines = 0; + createProcessNetworkHeader(_processNetworkCounter); + String partString = String.format("%03d", + _processNetworkCounter); + String processNetworkFilename = _dir + _delimiter + + "processnetwork_part" + partString + ".cpp"; + + _file = new FileOutputStream(processNetworkFilename); + _pn = new CodePrintStream(_file); + _fileOpen = true; + + _pn.printPrefixln("#include \"processnetwork_part" + + partString + ".h\""); + _pn.println(); + + _pn.printPrefixln("processnetwork_part" + partString + + "::processnetwork_part" + partString + "() {"); + _pn.printPrefixln(" //nothing to do"); + _pn.printPrefixln("}"); + _pn.println(); + _pn.printPrefixln("processnetwork_part" + partString + + "::~processnetwork_part" + partString + "() {"); + _pn.printPrefixln(" //nothing to do"); + _pn.printPrefixln("}"); + _pn.println(); + _pn.printPrefixln("void processnetwork_part" + partString + + "::create("); + _pn.printPrefixln(" std::map *processes)"); + _pn.printLeftBracket(); + } + + _pn.printPrefixln(nextLine); + _numberOfLines++; + } + + /** + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + try { + for (Port p : x.getPortList()) { + Port peerPort = (Port)(p.getPeerPort()); + Resource peerResource = p.getPeerResource(); + String codeLine = ""; + if (peerPort.getRange() != null) { + if (p.isOutPort()) { + codeLine = peerResource.getName() + + "->INPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), ""); + } + else if (p.isInPort()) { + codeLine = peerResource.getName() + + "->OUTPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), ""); + } + } + else { + if (p.isOutPort()) { + codeLine = peerResource.getName() + + "->INPORT_" + peerPort.getName(); + } + else if (p.isInPort()) { + codeLine = peerResource.getName() + + "->OUTPORT_" + peerPort.getName(); + } + } + printProcessNetwork(codeLine + " = " + x.getName() + ";"); + } + } + catch (Exception e) { + System.out.println("PipeAndFilterModuleVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected CodePrintStream _code = null; + protected String _dir = null; + protected boolean _fileOpen = false; + protected int _processNetworkCounter = 0; + protected int _numberOfLines = 0; + protected static final int MAX_NUMBER_OF_LINES = 1000; + protected OutputStream _file; + protected CodePrintStream _pn; +} + diff --git a/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterProcessVisitor.java b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterProcessVisitor.java new file mode 100644 index 0000000..a1ec83b --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterProcessVisitor.java @@ -0,0 +1,60 @@ +/* $Id: PipeAndFilterProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.PipeAndFilter; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; +import dol.visitor.hds.HdsProcessVisitor; + +/** + * + */ +public class PipeAndFilterProcessVisitor extends PNVisitor { + + /** + * Constructor. + */ + public PipeAndFilterProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + HdsProcessVisitor visitor = new HdsProcessVisitor(_dir); + x.accept(visitor); + + try { + String filename = _dir + _delimiter + "wrappers.h"; + OutputStream file = new FileOutputStream(filename); + _wrapperHeader = new CodePrintStream(file); + _wrapperHeader.printPrefixln("#ifndef WRAPPERS_H"); + _wrapperHeader.printPrefixln("#define WRAPPERS_H"); + _wrapperHeader.println(); + Vector pList = new Vector(); + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + _wrapperHeader.printPrefixln("#include \"" + + p.getBasename() + "_wrapper.h\""); + } + } + _wrapperHeader.println(); + _wrapperHeader.printPrefixln("#endif"); + } catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + CodePrintStream _wrapperHeader; +} diff --git a/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterVisitor.java b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterVisitor.java new file mode 100644 index 0000000..c545a77 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/PipeAndFilterVisitor.java @@ -0,0 +1,96 @@ +/* $Id: PipeAndFilterVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.PipeAndFilter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * + */ +public class PipeAndFilterVisitor extends PNVisitor { + + /** + * Constructor. + */ + public PipeAndFilterVisitor(String packageName) { + _packageName = packageName; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + try { + _generateDirHierarchy(); + + x.accept(new PipeAndFilterMakefileVisitor(_srcDir)); + x.accept(new PipeAndFilterModuleVisitor(_srcDir)); + x.accept(new PipeAndFilterProcessVisitor(_wrapperDir)); + + } catch (Exception e) { + System.out.println(" SystemC PN Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + + } + + /** + * + */ + private void _generateDirHierarchy() + throws IOException, FileNotFoundException { + + File dir = new File(_packageName); + dir.mkdirs(); + + _srcDir = _packageName + _delimiter + _srcDirName; + dir = new File(_srcDir); + dir.mkdirs(); + + _libDir = _srcDir + _delimiter + _libDirName; + dir = new File(_libDir); + dir.mkdirs(); + + _processDir = _srcDir + _delimiter + _processDirName; + dir = new File(_processDir); + dir.mkdirs(); + + _wrapperDir = _srcDir + _delimiter + _wrapperDirName; + dir = new File(_wrapperDir); + dir.mkdirs(); + + // copy library + String libraryPath = _ui.getMySystemCLib(); + libraryPath = libraryPath.replaceAll("systemC", + "PipeAndFilter"); + File source = new File(libraryPath); + File destination = new File(_libDir); + new Copier().copy(source, destination); + + //copy process src code + source = new File(_srcDirName); + destination = new File(_processDir); + new Copier().copy(source, destination); + } + + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; + + protected String _libDir = ""; + protected static String _libDirName = "lib"; + + protected String _processDir = ""; + protected static String _processDirName = "processes"; + + protected String _wrapperDir = ""; + protected static String _wrapperDirName = "wrappers"; +} + diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Condition.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/Condition.cpp new file mode 100644 index 0000000..4525b37 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Condition.cpp @@ -0,0 +1,31 @@ +#include "Condition.h" + +Condition::Condition(Mutex *mutex) { + //std::cout << "Create Condition." << std::endl; + _mutex = mutex; + _condition = new pthread_cond_t; + pthread_cond_init(_condition, NULL); +} + + +Condition::~Condition() { + //std::cout << "Delete Condition." << std::endl; + pthread_cond_destroy(_condition); + delete _condition; + //std::cout << "Deleted Condition." << std::endl; +} + + +void Condition::notify() { + pthread_cond_signal(_condition); +} + + +void Condition::notifyAll() { + pthread_cond_broadcast(_condition); +} + + +void Condition::wait() { + pthread_cond_wait(_condition, _mutex->getPThreadMutex()); +} diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Condition.h b/dol/src/dol/visitor/PipeAndFilter/lib/Condition.h new file mode 100644 index 0000000..c669892 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Condition.h @@ -0,0 +1,21 @@ +#ifndef _CONDITION_H_ +#define _CONDITION_H_ + +#include +#include +#include "Mutex.h" + +class Condition { + public: + Condition(Mutex *mutex); + virtual ~Condition(); + virtual void notify(); + virtual void notifyAll(); + virtual void wait(); + + protected: + Mutex *_mutex; + pthread_cond_t *_condition; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Event.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/Event.cpp new file mode 100644 index 0000000..52490b2 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Event.cpp @@ -0,0 +1,73 @@ +#include "Event.h" + +Event::Event() { + _name = "Event"; + //std::cout << "Create " << _name << "." << std::endl; + _mutex = new Mutex(); + _condition = new Condition(_mutex); + _waitMutex = new Mutex(); + _waitCondition = new Condition(_waitMutex); + _pendingWait = false; +} + + +Event::Event(std::string name) { + _name = "Event " + name; + //std::cout << "Create " << _name << "." << std::endl; + _mutex = new Mutex(); + _condition = new Condition(_mutex); + _waitMutex = new Mutex(); + _waitCondition = new Condition(_waitMutex); + _pendingWait = false; +} + + +Event::~Event() { + //std::cout << "Delete " << _name << "." << std::endl; + delete _condition; + delete _waitCondition; + delete _mutex; + delete _waitMutex; + //std::cout << "Deleted " << _name << "." << std::endl; +} + + +void Event::notify() { + _mutex->lock(); + _condition->notify(); + _mutex->unlock(); +} + + +void Event::notifyAll() { + _mutex->lock(); + _condition->notifyAll(); + _mutex->unlock(); +} + + +void Event::wait() { + _mutex->lock(); + _pendingWait = true; + _waitMutex->lock(); + _waitCondition->notify(); + _waitMutex->unlock(); + _condition->wait(); + _pendingWait = false; + _mutex->unlock(); +} + + +void Event::notifyAfterWait() { + _mutex->lock(); + + if (!_pendingWait) { + _waitMutex->lock(); + _mutex->unlock(); + _waitCondition->wait(); + _mutex->lock(); + _waitMutex->unlock(); + } + _condition->notify(); + _mutex->unlock(); +} diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Event.h b/dol/src/dol/visitor/PipeAndFilter/lib/Event.h new file mode 100644 index 0000000..51d14e8 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Event.h @@ -0,0 +1,30 @@ +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include +#include +#include +#include "Mutex.h" +#include "Condition.h" + +class Event { + public: + Event(); + Event(std::string name); + Event(Mutex *mutex); + virtual ~Event(); + virtual void notify(); + virtual void notifyAll(); + virtual void wait(); + virtual void notifyAfterWait(); + + protected: + Mutex *_mutex; + Condition *_condition; + Mutex *_waitMutex; + Condition *_waitCondition; + bool _pendingWait; + std::string _name; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.cpp new file mode 100644 index 0000000..a88fd98 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.cpp @@ -0,0 +1,229 @@ +#include "Fifo.h" + +/** + * + */ +Fifo::Fifo(char* name, unsigned size = 18) { + //std::cout << "Create Fifo." << std::endl; + //except at the beginning, _head and _tail must never overlap, + //otherwise one does not know whether the buffer is full or + //empty. to have nevertheless a buffer with the given capacity, + //a buffer with one more element is allocated. + _size = size + 1; + _buffer = new char[_size]; + _head = 0; + _tail = 0; + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + _mutex = new Mutex(); + _readCondition = new Condition(_mutex); + _writeCondition = new Condition(_mutex); +} + +/** + * + */ +Fifo::~Fifo() { + //std::cout << "Delete Fifo." << std::endl; + if (_buffer) { + delete _buffer; + } + if (_name) { + delete _name; + } + if (_readCondition) { + delete _readCondition; + } + if (_writeCondition) { + delete _writeCondition; + } + if (_mutex) { + delete _mutex; + } + + _buffer = 0; + _head = 0; + _tail = 0; + _name = 0; + _readCondition = 0; + _writeCondition = 0; + _mutex = 0; + //std::cout << "Deleted Fifo." << std::endl; +} + +/** + * + */ +unsigned Fifo::read(void *destination, unsigned len) { + char* buffer = (char*)destination; + unsigned read = 0; + //std::cout << "Try to read " << len << " bytes from Fifo " << _name << "." << std::endl; + + while (read < len) { + _mutex->lock(); + while (used() == 0) { + _writeCondition->wait(); + } + _mutex->unlock(); + + if ((len - read) < used()) { + while (read < len) { + _mutex->lock(); + unsigned tocopy = (len - read + _tail >= _size) ? _size - _tail : len - read; + memcpy(buffer, _buffer + _tail, tocopy); + _tail = (_tail + tocopy) % _size; + read += tocopy; + buffer += tocopy; + _mutex->unlock(); + } + _readCondition->notify(); + } else { + _mutex->lock(); + *buffer++ = *(_buffer + _tail % _size); + _tail = (_tail + 1) % _size; + read++; + _mutex->unlock(); + _readCondition->notify(); + } + } + + //std::cout << "Read " << read << " bytes from Fifo " << _name << "." << std::endl; + return read; +} + +/** + * + */ +unsigned Fifo::write(const void *source, unsigned len) { + char* buffer = (char*)source; + unsigned write = 0; + //std::cout << "Try to write " << len << " bytes to Fifo " << _name << std::endl; + + while (write < len) { + _mutex->lock(); + while (unused() == 0) { + _readCondition->wait(); + } + _mutex->unlock(); + + if ((len - write) < unused()) { + while (write < len) { + unsigned tocopy = (len - write + _head >= _size) ? _size - _head : len - write; + _mutex->lock(); + memcpy(_buffer + _head, buffer, tocopy); + _head = (_head + tocopy) % _size; + write += tocopy; + buffer += tocopy; + _mutex->unlock(); + } + _writeCondition->notify(); + } else { + _mutex->lock(); + *(_buffer + (unsigned)(_head) % _size) = *buffer++; + _head = (_head + 1) % _size; + write++; + _mutex->unlock(); + _writeCondition->notify(); + } + } + //std::cout << "Wrote " << write << " bytes to Fifo " << _name << "." << std::endl; + return write; +} + +/** + * + */ +unsigned Fifo::size() const { + return (_size - 1); +} + +/** + * + */ +unsigned Fifo::unused() const { + return (_size - 1) - used(); +} + +/** + * + */ +unsigned Fifo::used() const { + if (_head >= _tail) { + return _head - _tail; + } + return _head + _size - _tail; +} + +/** + * + */ +char* Fifo::getName() const { + return _name; +} + +/** + * Test the implementation + */ +/* +#include +void* producer(void *fifo) +{ + const char *str = + "Visit www.systemc.org and see what SystemC can do for you today!\n"; + + while (*str) { + //printf("%c", *str); + ((Fifo*)fifo)->write((void*)str++, 4); + } + printf("\nproducer returns.\n"); + return 0; +} + +void* consumer(void *fifo) +{ + char c; + while (c != '\n') { + ((Fifo*)fifo)->read(&c, 4); + std::cout << c << std::flush; + + if (((Fifo*)fifo)->used() == 1) + std::cout << "<1>" << std::flush; + if (((Fifo*)fifo)->used() == 9) + std::cout << "<9>" << std::flush; + } + printf("\nconsumer returns.\n"); + return 0; +} + + +int main() { + Fifo *fifo = new Fifo("fifo", 3); + pthread_t *producer_thread = new pthread_t; + pthread_t *consumer_thread = new pthread_t; + + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, 131072); + + if (pthread_create(consumer_thread, &attributes, consumer, fifo)) { + std::cout << "Error: Could not start consumer." << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + pthread_attr_destroy(&attributes); + + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, 131072); + if (pthread_create(producer_thread, &attributes, producer, fifo)) { + std::cout << "Error: Could not start producer." << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + pthread_attr_destroy(&attributes); + + + pthread_join(*consumer_thread, 0); + delete fifo; + return 0; +} +*/ diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.h b/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.h new file mode 100644 index 0000000..e1adb03 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Fifo.h @@ -0,0 +1,31 @@ +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include +#include +#include "Mutex.h" +#include "Condition.h" + +class Fifo { + public: + Fifo(char* name, unsigned size); + virtual ~Fifo(); + + virtual unsigned read(void *destination, unsigned len); + virtual unsigned write(const void *source, unsigned len); + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + virtual char* getName() const; + + protected: + char *_buffer; + unsigned _head; + unsigned _tail; + unsigned _size; + char *_name; + Mutex *_mutex; + Condition *_readCondition, *_writeCondition; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.cpp new file mode 100644 index 0000000..b943ff3 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.cpp @@ -0,0 +1,48 @@ +#include "Mutex.h" + +Mutex::Mutex() { + //std::cout << "Create Mutex." << std::endl; + _mutex = new pthread_mutex_t; + pthread_mutex_init(_mutex, NULL); //_mutexAttribute); + + _localMutex = new pthread_mutex_t; + pthread_mutex_init(_localMutex, NULL); + + _lockCondition = new pthread_cond_t; + pthread_cond_init(_lockCondition, NULL); +} + + +Mutex::~Mutex() { + //std::cout << "Delete Mutex." << std::endl; + pthread_mutex_destroy(_mutex); + delete _mutex; + + pthread_mutex_destroy(_localMutex); + delete _localMutex; + + pthread_cond_destroy(_lockCondition); + delete _lockCondition; + //std::cout << "Deleted Mutex." << std::endl; +} + + +int Mutex::trylock() { + int success = pthread_mutex_trylock(_mutex); + return success; +} + + +void Mutex::lock() { + pthread_mutex_lock(_mutex); +} + + +void Mutex::unlock() { + pthread_mutex_unlock(_mutex); +} + + +pthread_mutex_t *Mutex::getPThreadMutex() const { + return _mutex; +} diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.h b/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.h new file mode 100644 index 0000000..a88268c --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Mutex.h @@ -0,0 +1,22 @@ +#ifndef _MUTEX_H_ +#define _MUTEX_H_ + +#include +#include + +class Mutex { + public: + Mutex(); + virtual ~Mutex(); + virtual void lock(); + virtual void unlock(); + virtual int trylock(); + virtual pthread_mutex_t *getPThreadMutex() const; + + protected: + pthread_mutex_t *_mutex; + pthread_mutex_t *_localMutex; + pthread_cond_t *_lockCondition; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.cpp new file mode 100644 index 0000000..fbba359 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.cpp @@ -0,0 +1,128 @@ +#include "ProcessWrapper.h" +#include "dolSupport.h" + +/** + * + */ +ProcessWrapper::ProcessWrapper(char* name) { + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + + _isDetached = false; + for (int i = 0; i < 4; i++) { + _iteratorIndex[i] = getIndex(_name, "_", i); + } +} + +/** + * + */ +ProcessWrapper::~ProcessWrapper() { + if (_name) { + delete _name; + } +} + +/** + * + */ +void ProcessWrapper::initialize() { + _process.init(&_process); +} + +/** + * + */ +int ProcessWrapper::fire() +{ + return _process.fire(&_process); +} + + +/** + * + */ +void ProcessWrapper::detach() { + _isDetached = true; +} + + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(const char* string, char* tokens, + int indexNumber) const { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + return -1; +} + + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} + + +/** + * Get the name of this process. + */ +char* ProcessWrapper::getName() const { + return _name; +} + + +/** + * + */ +#ifdef INCLUDE_PROFILER +void ProcessWrapper::addToProfile(const char *event, void *port, + int length) { + if (profiler_output_file != NULL) { + fprintf(profiler_output_file, "%u %s %s %p %d\n", + profiler_event_counter++, _name, event, port, + length); + + } else { + printf("profiler_output_file does not exist"); + } +} +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.h b/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.h new file mode 100644 index 0000000..da7335f --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/ProcessWrapper.h @@ -0,0 +1,55 @@ +#ifndef _PROCESSWRAPPER_H_ +#define _PROCESSWRAPPER_H_ + +#include +#include +#include "dol.h" +#include "Fifo.h" +#include "WindowedFifo.h" + +#ifdef INCLUDE_PROFILER +extern FILE *profiler_output_file; +extern unsigned int profiler_event_counter; +#endif + +class ProcessWrapper +{ + public: + ProcessWrapper(char* name); + virtual ~ProcessWrapper(); + virtual void initialize(); + virtual int fire(); + virtual bool isDetached() { return _isDetached; } + virtual void detach(); + virtual int getIndex(unsigned indexNumber) const; + virtual char* getName() const; + +#ifdef INCLUDE_PROFILER + virtual void addToProfile(const char *event, void *port, + int length); +#endif + +#ifdef INCLUDE_TRACE + int start_line; + int end_line; + char channel_name[NAME_LENGTH]; +#endif + +#ifdef INCLUDE_PERFORMANCE + int start_line; + int end_line; + CURRENT_TIME start_time; + CURRENT_TIME end_time; +#endif + + protected: + char* _name; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; + virtual int getIndex(const char* string, char* tokens, + int indexNumber) const; +}; + +#endif + diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.cpp new file mode 100644 index 0000000..260729a --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.cpp @@ -0,0 +1,185 @@ +#include "Scheduler.h" + +typedef struct { + Scheduler *scheduler; + ProcessWrapper *process; +} ProcessArgs; + + +void* runProcessWrapperWrapper(void* arg) { + ProcessArgs *args = (ProcessArgs*)arg; + (args->scheduler)->runProcessWrapper(args->process); + delete args; + return 0; +} + + +void *scheduleWrapper(void *arg) { + ((Scheduler*)arg)->schedule(); + return 0; +} + + +Scheduler::Scheduler() { + //std::cout << "Create Scheduler." << std::endl; + _listsMutex = new Mutex(); + _listsCondition = new Condition(_listsMutex); + _mapsMutex = new Mutex(); + _mapsCondition = new Condition(_mapsMutex); + _processMap = new std::map(); + _notificationEventMap = new std::map(); + _scheduleList = new std::list(); + _detachList = new std::list(); + _stopScheduler = false; + _allStarted = false; +} + + +Scheduler::~Scheduler() { + delete _listsCondition; + delete _listsMutex; + delete _mapsCondition; + delete _mapsMutex; + delete _processMap; + delete _notificationEventMap; + delete _scheduleList; + delete _detachList; +} + + +void Scheduler::registerProcess(ProcessWrapper *process) { + pthread_t *newThread = new pthread_t; + _processMap->insert(std::pair + (process, newThread)); + Event *newNotificationEvent = + new Event("notificationEvent for " + std::string(process->getName())); + _notificationEventMap->insert(std::pair + (process, newNotificationEvent)); + process->initialize(); +} + + +void Scheduler::run() { + _mapsMutex->lock(); + + pthread_t scheduleThread; + if (pthread_create(&scheduleThread, NULL, scheduleWrapper, this)) { + std::cout << "Error: Could not start scheduler thread." + << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + std::map::iterator iterator; + for (iterator = _processMap->begin(); + iterator != _processMap->end(); + iterator++) { + + pthread_t *thread = (*iterator).second; + + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, 131072); + + ProcessArgs *args = new ProcessArgs; + args->scheduler = this; + args->process = (*iterator).first; + //std::cout << "Starting process " + // << ((ProcessWrapper*)args->process)->getName() + // << "." << std::endl; + if (pthread_create(thread, &attributes, runProcessWrapperWrapper, args)) { + std::cout << "Error: Could not start thread for process " + << ((ProcessWrapper*)(args->process))->getName() + << "." << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + pthread_attr_destroy(&attributes); + + //std::cout << "Started process " + // << ((ProcessWrapper*)(args->process))->getName() + // << "." << std::endl; + } + _mapsMutex->unlock(); + + //std::cout << "Started all registered processes." << std::endl; + _allStarted = true; + _listsCondition->notify(); + //std::cout << "Wait until scheduler has stopped." << std::endl; + + //wait until all processes have been detached + pthread_join(scheduleThread, 0); + + //std::cout << "Scheduler has stopped." << std::endl; +} + + +void Scheduler::runProcessWrapper(ProcessWrapper *process) { + Event *_notificationEvent = (*_notificationEventMap)[process]; + + while (!process->isDetached()) { + _listsMutex->lock(); + _scheduleList->push_back(process); + _listsCondition->notify(); + _listsMutex->unlock(); + _notificationEvent->wait(); + process->fire(); + } + + _listsMutex->lock(); + _detachList->push_back(process); + _listsMutex->unlock(); + _listsCondition->notify(); +} + + +void Scheduler::detachProcess(ProcessWrapper *process) { + _mapsMutex->lock(); + pthread_t *threadToKill = (*_processMap)[process]; + pthread_join(*threadToKill, 0); + delete threadToKill; + _processMap->erase(process); + + Event *notificationEventToKill = (*_notificationEventMap)[process]; + delete notificationEventToKill; + _notificationEventMap->erase(process); + + if (_processMap->empty()) { + //std::cout << "No processes left in process map. Terminate." + // << std::endl; + _stopScheduler = true; + } + _mapsMutex->unlock(); +} + + +void Scheduler::schedule() { + _listsMutex->lock(); + + while(!_stopScheduler) { + _listsCondition->wait(); + + if (_allStarted) { + std::list::iterator listIterator; + for (listIterator = _detachList->begin(); + listIterator != _detachList->end(); + listIterator++) { + ProcessWrapper *process = (*listIterator); + detachProcess(process); + //std::cout << "Scheduler detached process " + // << process->getName() << "." << std::endl; + } + _detachList->clear(); + + for (listIterator = _scheduleList->begin(); + listIterator != _scheduleList->end(); + listIterator++) { + ProcessWrapper *process = (*listIterator); + ((Event*)(*_notificationEventMap)[process])->notifyAfterWait(); + } + _scheduleList->clear(); + } + } + + _listsMutex->unlock(); + //std::cout << "Stopped scheduler." << std::endl; +} diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.h b/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.h new file mode 100644 index 0000000..791f6f0 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/Scheduler.h @@ -0,0 +1,35 @@ +#ifndef _SCHEDULER_H_ +#define _SCHEDULER_H_ + +#include +#include +#include +#include "Event.h" +#include "ProcessWrapper.h" + +class Scheduler +{ + public: + Scheduler(); + virtual ~Scheduler(); + + void registerProcess(ProcessWrapper *process); + void run(); + void runProcessWrapper(ProcessWrapper *process); + void detachProcess(ProcessWrapper *process); + void schedule(); + + protected: + Mutex *_listsMutex; + Condition *_listsCondition; + Mutex *_mapsMutex; + Condition *_mapsCondition; + std::map *_processMap; + std::map *_notificationEventMap; + std::list *_scheduleList; + std::list *_detachList; + bool _stopScheduler; + bool _allStarted; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.cpp new file mode 100644 index 0000000..ddeb970 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.cpp @@ -0,0 +1,304 @@ +#include "WindowedFifo.h" +#include + +/** + * + */ +WindowedFifo::WindowedFifo(char* name, unsigned size = 20) { + //std::cout << "Create WindowedFifo." << std::endl; + _size = size; + _buffer = new char[_size]; + _head = 0; + _tail = 0; + _headRoom = 0; + _tailRoom = 0; + _use = 0; + //indicates whether Fifo is empty or full if _head == _tail + //_isFull = false; + _isHeadReserved = false; + _isTailReserved = false; + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + _mutex = new Mutex(); + _readCondition = new Condition(_mutex); + _writeCondition = new Condition(_mutex); +} + +/** + * + */ +WindowedFifo::~WindowedFifo() { + //std::cout << "Delete WindowedFifo." << std::endl; + if (_buffer) { + delete _buffer; + } + if (_name) { + delete _name; + } + if (_readCondition) { + delete _readCondition; + } + if (_writeCondition) { + delete _writeCondition; + } + if (_mutex) { + delete _mutex; + } + _buffer = 0; + _head = 0; + _tail = 0; + _name = 0; + _use = 0; + _readCondition = 0; + _writeCondition = 0; + _mutex = 0; + //std::cout << "Deleted WindowedFifo." << std::endl; +} + +/** + * + */ +unsigned WindowedFifo::reserve(void** dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to reserve " << len << " bytes." << std::endl; + + //can only reserve once piece at a time + if (_isHeadReserved) { + *destination = 0; + return 0; + } + + _mutex->lock(); + while (unused() == 0) { + _readCondition->wait(); + } + + //reserve at most as much memory as still available in the buffer + unsigned write = (len <= _size - _use ? len : _size - _use); + + if ( write > 0 ) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_head + write > _size) { + write = _size - _head; + } + + _headRoom = (_head + write) == _size? 0 : _head + write; + *destination = &(_buffer[_head]); + _isHeadReserved = true; + + //the following comparison is unsafe in a multi-threaded + //environment and potentially leads to race-conditions + /*if (_headRoom == _tail) { + _isFull = true; + } else { + _isFull = false; + }*/ + } + _writeReserve = write; + _mutex->unlock(); + + //std::cout << "Reserved " << write << " bytes." << std::endl; + return write; +} + +/** + * + */ +void WindowedFifo::release() { + if (_isHeadReserved) { + //std::cout << "Released " << _headRoom - _head << " bytes." << std::endl; + _head = _headRoom; + _use += _writeReserve; + _isHeadReserved = false; + _writeCondition->notify(); + } +} + +/** + * + */ +unsigned WindowedFifo::capture(void **dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to capture " << len << " bytes." << std::endl; + + if (_isTailReserved) { + //std::cout << "Only one attempt to capture allowed." << std::endl; + *destination = 0; + return 0; + } + + _mutex->lock(); + while (used() == 0) { + _writeCondition->wait(); + } + + //capture at most as much data as available in the buffer + unsigned read = (len <= _use ? len : _use); + + if (read > 0) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_tail + read> _size) { + read = _size - _tail; + } + + _tailRoom = (_tail + read) == _size ? 0 : _tailRoom = _tail + read; + *destination = &(_buffer[_tail]); + _isTailReserved = true; + } + _readReserve = read; + _mutex->unlock(); + //std::cout << "Captured " << read << " bytes." << std::endl; + + return read; +} + +/** + * + */ +void WindowedFifo::consume() { + _mutex->lock(); + if (_isTailReserved) { + //std::cout << "Consumed " << _tailRoom - _tail << " bytes." << std::endl; + _tail = _tailRoom; + //_isFull = false; + _use -= _readReserve; + _isTailReserved = false; + _readCondition->notify(); + } + _mutex->unlock(); +} + +/** + * + */ +unsigned WindowedFifo::size() const { + return _size; +} + +/** + * + */ +unsigned WindowedFifo::unused() const { + return _size - _use; +} + +/** + * + */ +unsigned WindowedFifo::used() const { + return _use; + /*if (_headRoom > _tail) { + return _headRoom - _tail; + } else if (_headRoom == _tail) { + if (_isFull == true) { + return _size; + } else { + return 0; + } + } + return _headRoom + _size - _tail;*/ +} + +/** + * + */ +char* WindowedFifo::getName() const { + return _name; +} + +/** + * Test the implementation + */ +/* +#include +#include +#define LENGTH 10 + +void* producer(void *fifo) +{ + WindowedFifo* wfifo = (WindowedFifo*)fifo; + for (int j = 0; j < LENGTH; j++) { + //std::cout << "write " << i << " to Fifo. "; + int *buf1; + int write = wfifo->reserve((void**)&buf1, sizeof(int)); + + if (write == sizeof(int)) { + *buf1 = j; + wfifo->release(); + //std::cout << "used: " << std::setw(2) << wfifo->used() + // << ", unused: " << std::setw(2) << wfifo->unused() + // << ", size: " << std::setw(2) << wfifo->size() + // << std::endl; + } else { + std::cout << "Not successful: " << write << std::endl; + } + } + printf("producer returns.\n"); + return 0; +} + +void* consumer(void *fifo) +{ + WindowedFifo* wfifo = (WindowedFifo*)fifo; + for (int j = 0; j < LENGTH; j++) { + int* buf3; + int read = wfifo->capture((void**)&buf3, sizeof(int)); + if (read == sizeof(int)) { + std::cout << "read " << (unsigned)*buf3 << " from WindowedFifo "; + std::cout << "used: " << std::setw(2) << wfifo->used() + << ", unused: " << std::setw(2) << wfifo->unused() + << ", size: " << std::setw(2) << wfifo->size() + << std::endl; + wfifo->consume(); + } else { + std::cout << "Read nothing from WindowedFifo." << std::endl; + } + } + printf("consumer returns.\n"); + return 0; +} + +int main() { + WindowedFifo *wfifo = new WindowedFifo("fifo", 12); + + int* buf1; + int* buf2; + wfifo->reserve((void**)&buf1, 8); + *buf1 = 10; + *(buf1 + 1) = 20; + wfifo->release(); + wfifo->capture((void**)&buf2, 8); + std::cout << "read " << *buf2 << " " << *(buf2 + 1) << std::endl; + wfifo->consume(); + + pthread_t *producer_thread = new pthread_t; + pthread_t *consumer_thread = new pthread_t; + + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, 131072); + + if (pthread_create(consumer_thread, &attributes, consumer, wfifo)) { + std::cout << "Error: Could not start consumer." << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + pthread_attr_destroy(&attributes); + + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, 131072); + if (pthread_create(producer_thread, &attributes, producer, wfifo)) { + std::cout << "Error: Could not start producer." << std::endl; + std::cout << "Exit." << std::endl; + exit(1); + } + pthread_attr_destroy(&attributes); + + + pthread_join(*consumer_thread, 0); + delete wfifo; + return 0; +} +*/ diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.h b/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.h new file mode 100644 index 0000000..edab527 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/WindowedFifo.h @@ -0,0 +1,41 @@ +#ifndef _WINDOWEDFIFO_H_ +#define _WINDOWEDFIFO_H_ + +#include "Mutex.h" +#include "Condition.h" + +class WindowedFifo { + public: + WindowedFifo(char* name, unsigned size); + virtual ~WindowedFifo(); + + virtual unsigned reserve(void** destination, unsigned len); + virtual void release(); + + virtual unsigned capture(void** destination, unsigned len); + virtual void consume(); + + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + virtual char* getName() const; + + protected: + char *_buffer; + unsigned _head; + unsigned _tail; + unsigned _headRoom; + unsigned _tailRoom; + unsigned _size; + unsigned _use; + unsigned _writeReserve; + unsigned _readReserve; + //bool _isFull; + bool _isHeadReserved; + bool _isTailReserved; + char *_name; + Mutex *_mutex; + Condition *_readCondition, *_writeCondition; +}; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/dol.h b/dol/src/dol/visitor/PipeAndFilter/lib/dol.h new file mode 100644 index 0000000..8fbefe4 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/dol.h @@ -0,0 +1,39 @@ +#ifndef DOL_H +#define DOL_H + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * Define the DOL process handler scheme. + * - Local variables are defined in structure LocalState. Local + * variables may vary from different processes. + * - The ProcessInit function pointer points to a function which + * initializes a process. + * - The ProcessFire function pointer points to a function which + * performs the actual computation. The communication between + * processes is inside the ProcessFire function. + * - The WPTR is a placeholder for callback. One can just + * leave it blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.cpp b/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.cpp new file mode 100644 index 0000000..1359379 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.cpp @@ -0,0 +1,133 @@ +#include "dolSupport.h" +#include "ProcessWrapper.h" + +/** + * + */ +unsigned write(void *port, void *buf, unsigned len, DOLProcess *process) +{ + Fifo *fifo = static_cast(port); + char *str = static_cast(buf); + fifo->write((void*)str, len); + return len; +} + + +/** + * + */ +unsigned read(void *port, void *buf, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + char *str = static_cast(buf); + fifo->read((void*)str, len); + return len; +} + + +/** + * + */ +int wtest(void *port, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + return (fifo->unused() >= len) ? 1 : 0; +} + + +/** + * + */ +int rtest(void *port, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + return (fifo->used() >= len) ? 1 : 0; +} + + +/** + * + */ +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->reserve(destination, len); +} + +/** + * + */ +void release(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->release(); +} + +/** + * + */ +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->capture(destination, len); +} + +/** + * + */ +void consume(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->consume(); +} + + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast(p->wptr)->detach(); +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0) { + *port = (void**)((void**)base)[index0]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1) { + *port = (void**)((void**)base)[index0 * range1 + index1]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2) { + *port = (void**)((void**)base)[index0 * range1 * range2 + + index1 * range2 + index2]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3) { + *port = (void**)((void**)base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + + index2 * range3 + + index3]; +} diff --git a/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.h b/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.h new file mode 100644 index 0000000..3430033 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/lib/dolSupport.h @@ -0,0 +1,151 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include "dol.h" +#include "Fifo.h" +#ifdef INCLUDE_PERFORMANCE +#include "Performance_Extraction.h" +#elif INCLUDE_TRACE +#include "functional_trace.h" +#endif + +#ifdef INCLUDE_PERFORMANCE +#define DOL_write(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + end_time));\ + performance_extraction.add_computation_performance(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line,\ + &((static_cast(p->wptr))->start_time),\ + &((static_cast(p->wptr))->end_time));\ + write(port, buf, len, process);\ + (static_cast(p->wptr))->start_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + start_time)); } +#define DOL_read(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + end_time));\ + performance_extraction.add_computation_performance(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line,\ + &((static_cast(p->wptr))->start_time),\ + &((static_cast(p->wptr))->end_time));\ + read(port, buf, len, process);\ + (static_cast(p->wptr))->start_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + start_time)); } +#elif INCLUDE_TRACE +#define DOL_write(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + dol_functional_trace.create_computation_event(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line);\ + write(port, buf, len, process);\ + dol_functional_trace.create_write_event(\ + (static_cast(p->wptr))->basename(), len,\ + (static_cast(p->wptr))->channel_name);\ + (static_cast(p->wptr))->start_line = __LINE__; } +#define DOL_read(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + dol_functional_trace.create_computation_event(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line);\ + read(port, buf, len, process);\ + dol_functional_trace.create_read_event(\ + (static_cast(p->wptr))->basename(), len,\ + (static_cast(p->wptr))->channel_name);\ + (static_cast(p->wptr))->start_line = __LINE__; } +#else + #define DOL_write(port, buf, len, process) \ + write(port, buf, len, process) + #define DOL_read(port, buf, len, process) \ + read(port, buf, len, process) +#endif + +#define DOL_reserve(port, buf, size, process) \ + reserve(port, (void**)buf, size, process); + +#define DOL_release(port, process) \ + release(port, process); + +#define DOL_capture(port, buf, size, process) \ + capture(port, (void**)buf, size, process); + +#define DOL_consume(port, process) \ + consume(port, process); + +#define DOL_wtest(port, len, process) wtest(port, len, process) + +#define DOL_rtest(port, len, process) rtest(port, len, process) + +void DOL_detach(DOLProcess* p); + +//fifo access functions +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p); +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p); +int wtest(void *port, unsigned len, DOLProcess *process); +int rtest(void *port, unsigned len, DOLProcess *process); + +//windowed fifo access functions +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p); +void release(void* fifo, DOLProcess* p); +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p); +void consume(void* fifo, DOLProcess* p); + + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3); + +#define GETINDEX(dimension) \ + static_cast(p->wptr)->getIndex(dimension) + +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) Fifo *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#endif diff --git a/dol/src/dol/visitor/PipeAndFilter/package.html b/dol/src/dol/visitor/PipeAndFilter/package.html new file mode 100644 index 0000000..91e4bd2 --- /dev/null +++ b/dol/src/dol/visitor/PipeAndFilter/package.html @@ -0,0 +1,20 @@ + + + + + + +Code generator for functional simulation using threads for execution (no SystemC required). + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/Visitor.java b/dol/src/dol/visitor/Visitor.java new file mode 100644 index 0000000..3af1dd8 --- /dev/null +++ b/dol/src/dol/visitor/Visitor.java @@ -0,0 +1,9 @@ +/* $Id: Visitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor; + +/** + * This class is a marker Class, to indicate that derived classes are all + * visitor. + */ +public interface Visitor { +} diff --git a/dol/src/dol/visitor/cbe/CbeBuildFileVisitor.java b/dol/src/dol/visitor/cbe/CbeBuildFileVisitor.java new file mode 100644 index 0000000..4500d91 --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeBuildFileVisitor.java @@ -0,0 +1,109 @@ +/* $Id: CbeBuildFileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate the build + * file for the application on the CBE (i.e. a bash file for the CBE) + * + * @author lschor, 2008-10-30 + * + * Revision: + * 2008-10-30: Updated the file for the CBE + * 2008-11-08: Add double buffering + */ +public class CbeBuildFileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public CbeBuildFileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param pn process network + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "pncbe.sh"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + // General information / notes and commands + ps.println("#!/bin/bash"); + ps.println("clear"); + ps.println(); + ps.println("# Bash file to run a DOL application on the CBE"); + ps.println("# Start the CBE-Simulator and enter the " + + "following commands: "); + ps.println("# callthru source /pncbe.sh > pncbe.sh"); + ps.println("# for example: callthru source /opt/cell/sdk/src/" + + "tutorial/pn_test/pncbe.sh > pncbe.sh"); + ps.println("# chmod +x pncbe.sh"); + ps.println(); + ps.println("# To run the DOL application, use the following " + + "command: "); + ps.println("# ./pncbe.sh"); + ps.println(); + ps.println("# Folder in which the application is stored"); + ps.println("FOLDER=/opt/cell/sdk/src/tutorial/pn_test"); + ps.println(); + + // Go through each process and copy its data to the Cell + String subdirectory = ""; + String applicationName = ""; + Vector pList = new Vector(); + + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename) + && process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0) { + subdirectory = "spu_" + basename; + applicationName = subdirectory + "/spu_" + + basename + "_wrapper"; + + ps.println("# " + basename); + ps.println("if [ ! -d " + subdirectory + " ]; then"); + ps.println("\tmkdir " + subdirectory); + ps.println("fi;"); + ps.println("callthru source $FOLDER/" + + applicationName + " > " + applicationName); + ps.println("chmod +x " + applicationName); + ps.println(); + pList.add(basename); + } + } + + // Load also the main application to the CBE + ps.println("# Main program"); + ps.println("callthru source $FOLDER/ppu_main > ppu_main"); + ps.println("chmod +x ppu_main"); + ps.println(); + + // Run the main program + ps.println("# run the main program"); + ps.println("./ppu_main"); + } catch (Exception e) { + System.out.println("CbeMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/cbe/CbeConstantVisitor.java b/dol/src/dol/visitor/cbe/CbeConstantVisitor.java new file mode 100644 index 0000000..594673b --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeConstantVisitor.java @@ -0,0 +1,102 @@ +/* $Id: CbeConstantVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.HashMap; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.main.UserInterface; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a constant file + * + * @author lschor, 2008-11-08 + * + * Revision: + * 2008-11-08: Created + */ +public class CbeConstantVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public CbeConstantVisitor(String dir, HashMap portMap) { + _dir = dir; + _portMap = portMap; + } + + /** + * Visit process network. + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + _ui = UserInterface.getInstance(); + String filename = _dir + _delimiter + "lib" + _delimiter + + "constant.h"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + int numSpes = 0; + + for (Process p : x.getProcessList()) + { + if (p.getNumOfInports() > 0 && p.getNumOfOutports() > 0) + numSpes++; + } + + //create header section + _mainPS.println("// ========================"); + _mainPS.println("// constant.c file"); + _mainPS.println("// ========================"); + + //includes + _mainPS.println("#include "); + + //define the number of FIFO queues and the number of processes + _mainPS.println("#ifndef _CONSTANT_H_"); + _mainPS.println("#define _CONSTANT_H_"); + _mainPS.println(""); + _mainPS.println("#define NUM_PROCS " + + x.getProcessList().size()); + _mainPS.println("#define NUM_SPES " + numSpes); + _mainPS.println("#define NUM_FIFO " + + x.getChannelList().size()); + _mainPS.println(""); + _mainPS.println("#endif // _CONSTANT_H_ "); + _mainPS.println(""); + } + catch (Exception e) { + System.out.println("CbeModuleVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param x process that needs to be processed + */ + public void visitComponent(Process x) { + } + + /** + * + * @param x channel that needs to be processed + */ + public void visitComponent(Channel x) { + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected HashMap _portMap; +} diff --git a/dol/src/dol/visitor/cbe/CbeMakefileVisitor.java b/dol/src/dol/visitor/cbe/CbeMakefileVisitor.java new file mode 100644 index 0000000..45a4f2a --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeMakefileVisitor.java @@ -0,0 +1,151 @@ +/* $Id: CbeMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate a CBE + * package Makefile. + */ +public class CbeMakefileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public CbeMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param pn process network + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("##############################################"); + ps.println("# Main Makefile for DOL application on the CBE"); + ps.println("##############################################"); + ps.println(); + ps.println("# Subdirectories"); + + String subdirectories = ""; + Vector pList = new Vector(); + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename)) { + if (process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0) { + subdirectories += "spu_" + basename + " "; + } + pList.add(basename); + } + } + pList.clear(); + + String linkList = ""; + String srcList = ""; + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename)) { + if (!(process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0)) { + linkList += "ppu_" + basename + "/ppu_" + basename + + ".o "; + srcList += "$(wildcard ppu_" + basename + + "/*.c) "; + + } + pList.add(basename); + } + } + pList.clear(); + + ps.println("DIRS\t := " + subdirectories); + ps.println(""); + + ps.println("# General definitions:"); + ps.println("CC = ppu-g++"); + ps.println("CCFLAGS = -ftree-vectorize -O3 -maltivec " + + "-funroll-loops -mabi=altivec -mcpu=cell"); + ps.println("COMPILE = $(CC) $(CCFLAGS) -c"); + ps.println("LINK = $(CC) -lspe2 -lpthread"); + ps.println("CBE_INCLUDE = /opt/cell/sdk/src/include/ppu"); + ps.println("LIB_INC = -I lib -I . -I $(CBE_INCLUDE)"); + ps.println("RM = rm"); + ps.println("ECHO = echo"); + ps.println("EXE = ppu_main"); + ps.println(""); + ps.println("src := $(wildcard lib/*.c) $(wildcard *.c)"); + ps.println("srcAll := $(wildcard lib/*.c) " + + "$(wildcard lib/*.c) " + srcList); + ps.println("obj = $(src:.c=.o)"); + ps.println(""); + ps.println("$(EXE): $(obj) $(srcAll)"); + ps.println("\tfor d in $(DIRS); do (cd $$d; $(MAKE) ); done"); + + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename)) { + if (!(process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0)) { + ps.println("\tcd ppu_" + basename + + "; $(COMPILE) -o ppu_" + basename + + ".o ppu_" + basename + + "_wrapper.c -I .. -I ../lib;"); + } + pList.add(basename); + } + } + pList.clear(); + + ps.println("\t$(LINK) -o $(EXE) " + linkList + " $(obj)"); + ps.println(""); + ps.println("%.o :"); + ps.println("\t$(COMPILE) -o $(*D)/$(*F).o $(*D)/$(*F).c " + + "$(LIB_INC)"); + ps.println(""); + ps.println("clean:"); + ps.println("\tfor d in $(DIRS); do (cd $$d; $(MAKE) clean );" + + " done"); + ps.println("\trm $(obj) "); + + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename)) { + if (!(process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0)) + { + ps.println("\trm ppu_" + basename + "/ppu_" + + basename + ".o"); + } + pList.add(basename); + } + } + pList.clear(); + + ps.println("\trm ppu_main"); + ps.println(""); + + } catch (Exception e) { + System.out.println("CbeMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/cbe/CbeModuleVisitor.java b/dol/src/dol/visitor/cbe/CbeModuleVisitor.java new file mode 100644 index 0000000..7987bfc --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeModuleVisitor.java @@ -0,0 +1,417 @@ +/* $Id: CbeModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.main.UserInterface; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + * + * @author lschor, 2008-10-30 + * + * Revision: + * 2008-10-30: Updated the file for the CBE + * 2008-11-08: Add double buffering + * 2008-11-16: Add new fifo implementation and defines for measurement + * 2008-11-21: Sink/Source do not run on the SPE, but on the PPE (as Linux + * thread) + */ +public class CbeModuleVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public CbeModuleVisitor(String dir, HashMap portMap) { + _dir = dir; + _portMap = portMap; + } + + /** + * Visit process network. + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + _ui = UserInterface.getInstance(); + String filename = _dir + _delimiter + "ppu_main.c"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + //create header section + _mainPS.println("// ========================"); + _mainPS.println("// ppu_main.c file"); + _mainPS.println("// ========================"); + _mainPS.println(""); + + // Includes + _mainPS.println("#include \"ppu_main.h\""); + _mainPS.println(""); + + _mainPS.println(""); + _mainPS.println("// include main function for a workloop"); + _mainPS.println("#include \"ppu_main_workloop.h\""); + _mainPS.println(""); + + // Function to create and run one SPE thread + _mainPS.println("// create and run one SPE thread"); + _mainPS.println("void *spu_pthread(void *arg) {"); + _mainPS.println("\t spu_data_t *datp = (spu_data_t *)arg;"); + _mainPS.println("\t uint32_t entry = SPE_DEFAULT_ENTRY;"); + _mainPS.println("\t printf(\")PPE: spe thread starts\\n\");"); + _mainPS.println("\t if (spe_context_run(datp->spe_ctx, " + + "&entry, 0, datp->argp, NULL, NULL) < 0) {"); + _mainPS.println("\t\t perror (\"Failed running context\"); " + + "exit (1);"); + _mainPS.println("\t}"); + _mainPS.println("\t printf(\")PPE: spe thread stops\\n\");"); + _mainPS.println("\t pthread_exit(NULL);"); + _mainPS.println("}"); + _mainPS.println(""); + + // Declaration of the Header function for the PPE-Wrappers + Vector processList = new Vector(); + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!processList.contains(basename)) { + processList.add(basename); + if (!(p.getNumOfInports() > 0 + && p.getNumOfOutports() > 0)) + _mainPS.println("void *" + basename + + "_wrapper( void *ptr );"); + } + } + _mainPS.println(); + + // Create the port_id and the port_queue_id arrays to send + // over the DMA + for (Process process : x.getProcessList()) { + String processName = process.getName(); + _mainPS.println("volatile uint32_t "+ processName + + "_port_id[" + + roundDMA(process.getPortList().size()) + + "] __attribute__ ((aligned(16))); "); + _mainPS.println("volatile uint32_t " + processName + + "_port_queue_id[" + + roundDMA(process.getPortList().size()) + + "] __attribute__ ((aligned(16)));"); + _mainPS.println("volatile char " + processName + + "_name[256] __attribute__ ((aligned(16)));"); + } + _mainPS.println(); + + // Create the main function + _mainPS.println("int main()"); + _mainPS.println("{"); + + // For Measure + _mainPS.println("#ifdef MEASURE_APPLICATION"); + _mainPS.println("\tstruct timeval t_ppe_start, t_ppe_end;"); + _mainPS.println("\tgettimeofday(&t_ppe_start,NULL);"); + _mainPS.println("#endif"); + _mainPS.println(); + + _mainPS.println("#ifdef MEASURE_SET_UP_SPE_THREAD"); + _mainPS.println("\tstruct timeval t_ppe_setup_start, " + + "t_ppe_setup_end;"); + _mainPS.println("\tgettimeofday(&t_ppe_setup_start,NULL);"); + _mainPS.println("#endif"); + _mainPS.println(); + + // List with all process to be open + _mainPS.println("\tchar spe_names[NUM_SPES][60] = {"); + int count = 0; + for (Process process : x.getProcessList()) { + if (process.getNumOfInports() > 0 + && process.getNumOfOutports() > 0) { + count++; + String processName = process.getBasename(); + _mainPS.println("\t\t\"spu_" + processName + "/spu_" + + processName + "_wrapper\"" + + (count == x.getProcessList().size() + ? "" : ", ") ); + } + } + _mainPS.println("\t};"); + _mainPS.println(); + _mainPS.println("\t// Initialize the fifo, we use"); + _mainPS.println("\tint j; "); + _mainPS.println("\tfor (j = 0; j < NUM_FIFO; j++)"); + _mainPS.println("\t{"); + _mainPS.println("\t\tlocBuf[j] = " + + "(char*)malloc(MAXELEMENT * sizeof(char));"); + _mainPS.println("\t\tlocBufCount[j] = 0;"); + _mainPS.println("\t\tlocBufStart[j] = 0;"); + _mainPS.println("\t\tpthread_mutex_init(&(mutex[j]), NULL);"); + _mainPS.println("\t}"); + + //connect ports to channels + HashMap channel_map = + new HashMap(); + + int j = 0; + for (Channel c : x.getChannelList()) { + channel_map.put(c, j++); + } + + // Init the SPE control structure + _mainPS.println("\t//Initiate SPEs control structure"); + _mainPS.println("\tint num = 0; "); + _mainPS.println("\tfor( num=0; num 0 + && process.getNumOfOutports() > 0) { + _mainPS.println("\tctx[" + j + "]" + + ".port_id = (uint64_t)" + + processName + "_port_id;"); + _mainPS.println("\tctx[" + j + "]" + + ".port_queue_id = (uint64_t)" + + processName + "_port_queue_id;"); + _mainPS.println("\tctx[" + j + "]" + + ".number_of_ports = " + i + ";"); + _mainPS.println("\tctx[" + j + "]" + + ".is_detached = 0;"); + _mainPS.println("\tstrcpy((char *)" + processName + + "_name, " + "\"" + + processName + "\");"); + _mainPS.println("\tctx[" + j + "]" + + ".processName = (uint64_t) " + processName + + "_name;"); + _mainPS.println("\tctx[" + j + "]" + + ".processNameLen = ((strlen((char *)" + + processName + "_name) + 15) & ~15);"); + _mainPS.println(); + j++; + } + // Process is Sink or source + else { + _mainPS.println("\tProcessWrapper *" + + process.getName() + + "_Process_Wrapper = (ProcessWrapper*)" + + "malloc(sizeof(ProcessWrapper)); "); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->port_id = " + processName + + "_port_id;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->port_queue_id = " + + processName + "_port_queue_id;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->number_of_ports = " + + i + ";"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->is_detached = 0;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->name = (char*)malloc(" + + "strlen(\"" + processName + "\"));"); + _mainPS.println("\tstrcpy(" + process.getName() + + "_Process_Wrapper->name, \"" + processName + + "\");"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->locBuf = locBuf;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->MAXELEMENT = " + + "MAXELEMENT;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->locBufCount = " + + "locBufCount;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->locBufStart = " + + "locBufStart;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->processFinished = " + + "&processFinished;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->mutex = mutex;"); + _mainPS.println("\t" + process.getName() + + "_Process_Wrapper->mutexProcessNr = " + + "&mutexProcessNr;"); + _mainPS.println(); + } + } + + _mainPS.println("\t// Loop on all SPEs and for each perform " + + "three steps:"); + _mainPS.println("\t// - create SPE context"); + _mainPS.println("\t// - open images of SPE programs into main " + + "storage"); + _mainPS.println("\t// variable store the " + + "executable name"); + _mainPS.println("\t// - Load SPEs objects into SPE context " + + "local store"); + _mainPS.println("\tfor( num=0; num 0 + && process.getNumOfOutports() > 0)) { + _mainPS.println("\tpthread_t thread_" + + process.getName() + ";"); + _mainPS.println("\tpthread_create( &thread_" + + process.getName() + ", NULL, " + + process.getBasename() + "_wrapper, " + + process.getName() + "_Process_Wrapper);"); + } + } + _mainPS.println("\t"); + + _mainPS.println("\t// create SPE pthreads"); + _mainPS.println("\tfor( num=0; num 16) { + return number + 16 - (number % 16); + } else if (number > 8) { + return 16; + } else if (number > 4) { + return 8; + } else if (number > 2) { + return 4; + } else if (number > 1) { + return 2; + } else { + return 1; + } + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected HashMap _portMap; +} diff --git a/dol/src/dol/visitor/cbe/CbeProcessVisitor.java b/dol/src/dol/visitor/cbe/CbeProcessVisitor.java new file mode 100644 index 0000000..17044e0 --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeProcessVisitor.java @@ -0,0 +1,340 @@ +/* $Id: CbeProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Copier; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate the main + * makefile for the application. + * + * @author lschor, 2008-10-30 + * + * Revision: + * 2008-10-30: Updated the file for the CBE + * 2008-11-08: Add double buffering + */ +public class CbeProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public CbeProcessVisitor(String dir, HashMap portMap) { + _dir = dir; + _portMap = portMap; + } + + /** + * + * @param x process network that needs to be processed + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } catch (Exception e) { + System.out.println("CbeProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Visit process. + * + * @param p process that needs to be processed + */ + public void visitComponent(Process p) + { + // Differ if the process is a source/sink or a normal process + // - Source/Sink: Mapped to the PPE + // - Normal process: Mapped to the SPE + + if (p.getNumOfInports() > 0 && p.getNumOfOutports() > 0) + { + createSPEWrapper(p); + } + else + { + createPPEWrapper(p); + } + } + + /** + * Creates a Wrapper for an SPE process + * + * @param p process that needs to be processed + */ + protected void createSPEWrapper(Process p) + { + try { + + // Create of each process an own folder + // add to this folder the source, the wrapper and a makefile + String processDir = _dir + _delimiter + "spu_" + + p.getBasename(); + File dir = new File(processDir); + dir.mkdirs(); + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + "spu_" + + p.getBasename() + "_wrapper.c"; + File process_file = new File(filename); + File pattern_file = new File(_dir + _delimiter + _tempDirName + + _delimiter + "spu_process_wrapper_template.c"); + new Copier().copyFile(pattern_file, process_file); + + String includes = ""; + for (SourceCode code : p.getSrcList()) { + includes += "#include \"" + code.getLocality() + "\"" + + System.getProperty("line.separator"); + } + + Sed sed = new Sed(); + // Replace the include of the main c-file + sed.sed(filename, "//#include \"@PROCESSNAME@.c\"", includes); + // Replace the process-name in the whole file + sed.sed(filename, "@PROCESSNAME@", p.getBasename()); + + // Go through all source files + for (SourceCode sourceCode : p.getSrcList()) { + // Copy the files to the new folder + String oldFilename = _dir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + filename = processDir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (new File(filename).exists()) { + new File(filename).delete(); + } + + new File(oldFilename).renameTo(new File(filename)); + + // Copy also the c-files + if ((oldFilename.substring(oldFilename.length() - 2)) + .equals(".h")) { + String newFileNameC = filename.substring(0, filename + .length() - 2) + ".c"; + if (new File(newFileNameC).exists()) { + new File(newFileNameC).delete(); + } + new File(oldFilename.substring( + 0, oldFilename.length() - 2) + + ".c").renameTo(new File(newFileNameC)); + } + + // Update the files for the DOL project + sed.sed(filename, "", "\"dol.h\""); + + // Create the port list + for (Port port : p.getPortList()) { + Integer portId = _portMap.get(port); + if (!port.getBasename().equals(port.getName())) { + for (Port port2 : p.getPortList()) { + if (port2.getBasename().equals( + port.getBasename())) { + if (_portMap.get(port2) < + _portMap.get(port)) { + portId = _portMap.get(port2); + } + } + } + } + + // Update the port list with the base names + sed.sed(filename, "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", "$1 " + portId); + } + + // Create the makefile for the process + createMakefile(p); + } + } catch (Exception e) { + System.out.println("CbeProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates a Wrapper for an SPE process + * + * @param p process that needs to be processed + */ + protected void createPPEWrapper(Process p) + { + try { + // Create of each process an own folder + // add to this folder the source, the wrapper and a makefile + String processDir = _dir + _delimiter + "ppu_" + + p.getBasename(); + File dir = new File(processDir); + dir.mkdirs(); + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + "ppu_" + + p.getBasename() + "_wrapper.c"; + File process_file = new File(filename); + File pattern_file = new File(_dir + _delimiter + _tempDirName + + _delimiter + "ppu_process_wrapper_template.c"); + new Copier().copyFile(pattern_file, process_file); + + String includes = ""; + for (SourceCode code : p.getSrcList()) { + includes += "#include \"" + code.getLocality() + "\"" + + System.getProperty("line.separator"); + } + + Sed sed = new Sed(); + // Replace the include of the main c-file + sed.sed(filename, "//#include \"@PROCESSNAME@.c\"", includes); + // Replace the process-name in the whole file + sed.sed(filename, "@PROCESSNAME@", p.getBasename()); + + // Go through all source files + for (SourceCode sourceCode : p.getSrcList()) { + // Copy the files to the new folder + String oldFilename = _dir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + filename = processDir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (new File(filename).exists()) { + new File(filename).delete(); + } + + new File(oldFilename).renameTo(new File(filename)); + + // Copy also the c-files + if ((oldFilename.substring(oldFilename.length() - 2)) + .equals(".h")) { + String newFileNameC = filename.substring(0, filename + .length() - 2) + + ".c"; + if (new File(newFileNameC).exists()) { + new File(newFileNameC).delete(); + } + new File(oldFilename.substring( + 0, oldFilename.length() - 2) + + ".c").renameTo(new File(newFileNameC)); + } + + // Update the files for the DOL project + sed.sed(filename, "", "\"dol.h\""); + + // Create the port list + for (Port port : p.getPortList()) { + Integer portId = _portMap.get(port); + if (!port.getBasename().equals(port.getName())) { + for (Port port2 : p.getPortList()) { + if (port2.getBasename().equals( + port.getBasename())) { + if (_portMap.get(port2) < + _portMap.get(port)) { + portId = _portMap.get(port2); + } + } + } + } + + // Update the port list with the base names + sed.sed(filename, "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", "$1 " + portId); + } + + } + } catch (Exception e) { + System.out.println("CbeProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create the makefile for a special process -> Each subprocess + * gets its own makefile + * + * @param p process for which the makefile should be created + */ + protected void createMakefile(Process p) { + try { + // Directory of the process + String processDir = _dir + _delimiter + "spu_" + + p.getBasename(); + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + "Makefile"; + // File makefile = new File(filename); + + OutputStream file; + + file = new FileOutputStream(filename); + + PrintStream _makefilePS = new CodePrintStream(file); + + _makefilePS.println("# Makefile for process " + p.getName()); + _makefilePS.println(""); + + String dependency = "all: spu_" + p.getBasename() + + "_wrapper.c ../lib/ProcessWrapper.h " + + "../lib/ProcessFifo.h"; + + for (SourceCode code: p.getSrcList()) + { + dependency += " " + code.getLocality(); + } + + _makefilePS.println(dependency); + _makefilePS.println("\t spu-g++ -I .. -I ../lib -g -o spu_" + + p.getBasename() + "_wrapper spu_" + p.getBasename() + + "_wrapper.c -ftree-vectorize -lm -mtune=cell -O3 " + + "-fmodulo-sched -funroll-loops -ffast-math"); + _makefilePS.println("clean: "); + _makefilePS.println("\t rm spu_" + p.getBasename() + + "_wrapper"); + _makefilePS.println(); + + } catch (FileNotFoundException e) { + System.out.println("CbeProcessVisitor - Makefile: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected HashMap _portMap; + + protected static String _libDirName = "lib"; + protected static String _tempDirName = "template"; +} diff --git a/dol/src/dol/visitor/cbe/CbeVisitor.java b/dol/src/dol/visitor/cbe/CbeVisitor.java new file mode 100644 index 0000000..d70b347 --- /dev/null +++ b/dol/src/dol/visitor/cbe/CbeVisitor.java @@ -0,0 +1,183 @@ +/* $Id: CbeVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.cbe; + +import java.io.File; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a CBE package. + */ +public class CbeVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param packageName name of the Cbe directory + */ + public CbeVisitor(String packageName) { + _packageName = packageName; + } + + /** + * Visit process network. + * + * @param pn process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork pn) { + try { + File dir = new File(_packageName); + dir.mkdirs(); + + // Create the library + File lib = new File(_packageName + _delimiter + "lib"); + lib.mkdirs(); + + //copy library files + File source = new File(_ui.getMySystemCLib(). + replaceAll("systemC", "cbe").replace("%20", " ")); + new Copier().copy(source, lib); + + // Create the template + File template = new File(_packageName + _delimiter + + "template"); + template.mkdirs(); + + //copy the templates + source = new File(_ui.getMySystemCLib(). + replaceAll("systemC", "cbe"). + replace("lib", "template").replace("%20", " ")); + new Copier().copy(source, template); + + // Some library files must be copied to the main directory + (new File(lib.getPath() + _delimiter + "ppu_main.h")). + renameTo(new File (dir.getPath() + _delimiter + + "ppu_main.h")); + + //copy process source code + source = new File(_srcDirName.replace("%20", " ")); + new Copier().copy(source, dir); + + createPortMap(pn); + pn.accept(new CbeMakefileVisitor(_packageName)); + pn.accept(new CbeBuildFileVisitor(_packageName)); + pn.accept(new CbeProcessVisitor(_packageName, _portMap)); + pn.accept(new CbeModuleVisitor(_packageName, _portMap)); + pn.accept(new CbeConstantVisitor(_packageName, _portMap)); + } + catch (Exception e) { + System.out.println("CbeVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create a hashmap which maps each port of the given process network + * to an integer. For each process, ports are numbered with integers + * starting from 0. + * + * @param pn process network for which the map should be generated + */ + protected void createPortMap(ProcessNetwork pn) { + _portMap = new HashMap(); + + for (Process process : pn.getProcessList()) { + int portCount = 0; + Vector portList = process.getPortList(); + Vector portNameList = new Vector(); + portNameList.clear(); + HashMap portMap = + new HashMap(); + portMap.clear(); + + for (int i = 0; i < portList.size(); i++) { + //treat single ports differently than iterated ports + String portName = portList.elementAt(i).getName(); + String baseName = portList.elementAt(i).getBasename(); + + if (portName.equals(baseName)) { + portNameList.add(portName); + portMap.put(portName, portCount++); + } else { + String range_indices = + portList.elementAt(i).getRange(); + Vector range_indices_values = + getIndex(range_indices, ";"); + + String port_indices = portName; + port_indices.replaceAll(baseName, ""); + Vector port_indices_values = + getIndex(port_indices, "_"); + + if (!portNameList.contains(baseName)) { + portNameList.add(baseName); + portMap.put(baseName, portCount); + + int size = 1; + for (int j = 0; + j < range_indices_values.size(); j++) { + size *= range_indices_values.elementAt(j); + } + portCount += size; + } + + int portId = portMap.get(baseName); + for (int j = 0; j < port_indices_values.size(); j++) { + int weight = 1; + for (int k = j + 1; + k < range_indices_values.size(); k++) { + weight *= range_indices_values.elementAt(k); + } + portId += port_indices_values.elementAt(j) + * weight; + } + portMap.put(portName, portId); + } + } + + for (int i = 0; i < portList.size(); i++) { + _portMap.put(portList.elementAt(i), + portMap.get(portList.elementAt(i).getName())); + } + } + } + + /** + * Gets vector of indices of a string, where the index must be + * separated by the specified separator. + * examples: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param range string to parse + * @param separator delimiter of indices + * @return vector of indices + */ + protected Vector getIndex(String range, String separator) { + Vector indices = new Vector(); + String[] subranges = range.split(separator); + for (int i = 0; i < subranges.length; i++) { + try { + int value = Integer.valueOf(subranges[i]); + indices.add(value); + } catch (Exception e) { + continue; + } + } + return indices; + } + + protected HashMap _portMap; + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; +} diff --git a/dol/src/dol/visitor/cbe/lib/ProcessFifo.h b/dol/src/dol/visitor/cbe/lib/ProcessFifo.h new file mode 100644 index 0000000..8054cd2 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/ProcessFifo.h @@ -0,0 +1,219 @@ +/**************************************************************** + * FIFO Functions + * Creator: lschor, 2008-10-30 + * Description: Defines Main functions for a FIFO in the Local Store of a SPE + * + * Revision: + * - 2008-11-08: Created + * - 2008-11-15: Added Performance Estimation methods + */ + +#ifndef _PROCESS_FIFO_H_ +#define _PROCESS_FIFO_H_ + +// Maximum data allowed in the FIFO +static const int MAXELEMENT = 1024; + +// Varialbes to store a fifo-read process +uint32_t tag_id_read = 99; +uint32_t lenRead = 0; +uint32_t queueRead = 0; +char * resultRead; + +char *locBuf[NUM_FIFO]; +int locBufCount[NUM_FIFO]; +int locBufStart[NUM_FIFO]; + +// How many is free in a fifo +uint32_t freespaceQueue(int queue) +{ + return MAXELEMENT - locBufCount[queue]; +} + + +// Init all fifos +void initQueues() +{ + int j; + for (j = 0; j < NUM_FIFO; j++) + { + locBuf[j] = NULL; + locBufCount[j] = 0; + locBufStart[j] = 0; + } +} + +// Init a special queue +void initLocBuf(int queue) +{ + locBuf[queue] = NULL; + locBufCount[queue] = 0; + locBufStart[queue] = 0; +} + +// Gives the memory of the buffers free +void deinitLocBuf() +{ + int j; + for (j = 0; j < NUM_FIFO; j++) + { + if (locBuf[j] != NULL) + { + free(locBuf[j]); + } + } +} + +void updateLocBuf(int queue) +{ + // If the buffer is used the first time, allocate him + if (locBuf[queue]== NULL) + { + locBuf[queue] = (char *)malloc(MAXELEMENT * sizeof(char)); + } +#ifdef MEASURE_DOL_READ_START_DMA + double t_dol_read_start_dma = spu_read_decrementer(); +#endif + + // Double-Buffering --> Try to close the old read-process + if (tag_id_read != 99) + { + waittag(tag_id_read); + // Write the data you read into the queue; + int i; + + #ifdef MEASURE_DOL_READ_DOUBLEBUF + double t_dol_read_doublebuf = spu_read_decrementer(); + #endif + + // Has to write all elements at the beginning or write all elements at the end of the buffer + if (locBufStart[queueRead] + locBufCount[queueRead] > MAXELEMENT || locBufStart[queueRead] + locBufCount[queueRead] + lenRead < MAXELEMENT) + { + memcpy(&(locBuf[queueRead][(locBufStart[queueRead] + locBufCount[queueRead]) % MAXELEMENT]), resultRead, lenRead); + } + + // Otherwise something at the end and something at the beginning + else + { + // At the end of the buffer + memcpy(&(locBuf[queueRead][(locBufStart[queueRead] + locBufCount[queueRead])]), resultRead, MAXELEMENT - locBufStart[queueRead] - locBufCount[queueRead]); + + // At the beginning + memcpy(locBuf[queueRead], &(resultRead[MAXELEMENT - locBufStart[queueRead] - locBufCount[queueRead]]), lenRead - (MAXELEMENT - locBufStart[queueRead] - locBufCount[queueRead])); + } + + locBufCount[queueRead]+=lenRead; + + #ifdef MEASURE_DOL_READ_DOUBLEBUF + t_dol_read_doublebuf -= spu_read_decrementer(); + printf("DOL_READ_DOUBLEBUF_DMA;%f\n",t_dol_read_doublebuf * 1 / MEASURE_CPU); + #endif + + mfc_tag_release(tag_id_read); // release tag ID before exiting + _free_align(resultRead); + + // Send a okey about the data to the PPE + spu_write_out_mbox((uint32_t)(SPE_READ_SUC << 28)); // stalls mailbox is full. + tag_id_read = 99; + } + + uint32_t message = (SPE_READ_DEMAND << 28) | (queue << 16) | (freespaceQueue(queue)); + +#ifdef MEASURE_DOL_READ_HANDSHAKE + double t_dol_read_handshake = spu_read_decrementer(); +#endif + + spu_write_out_mbox((uint32_t)(message)); + + message = spu_read_in_mbox(); + uint32_t request = message >> 28; + lenRead = (0xffff & message); + + if (request != (uint32_t) 1) + { + return; + } + + if((tag_id_read=mfc_tag_reserve())==MFC_TAG_INVALID){ + printf("SPE: ERROR - can't reserve a tag ID\n"); return; + } + + // Wait for the response of the PPE --> The address where to read + uint32_t ea_mfc_h, ea_mfc_l; + uint64_t ea_mfc; + + ea_mfc_h = spu_read_in_mbox(); + ea_mfc_l = spu_read_in_mbox(); + ea_mfc = mfc_hl2ea(ea_mfc_h, ea_mfc_l); + +#ifdef MEASURE_DOL_READ_HANDSHAKE + t_dol_read_handshake -= spu_read_decrementer(); + printf("DOL_READ_HANDSHAKE;%f\n",t_dol_read_handshake * 1 / MEASURE_CPU); +#endif + +#ifdef MEASURE_DOL_READ_START_DMA + t_dol_read_start_dma -= spu_read_decrementer(); + printf("DOL_READ_START_DMA;%f\n",t_dol_read_start_dma * 1 / MEASURE_CPU); +#endif + + resultRead = (char *)_malloc_align(lenRead, 4); + +#ifdef MEASURE_DOL_READ_DMA + double t_dol_read_dma = spu_read_decrementer(); +#endif + + // Read the data from the address we got from the PPE + mfc_get(&(resultRead[0]), ea_mfc, lenRead, tag_id_read, 0, 0); +#ifdef MEASURE_DOL_READ_DMA + t_dol_read_dma -= spu_read_decrementer(); + printf("DOL_READ_DMA;%f\n",t_dol_read_dma * 1 / MEASURE_CPU); +#endif + + queueRead = queue; +} + +// Read from the local buffer +void readLocBuf(int queue, void *destination, int len) +{ +#ifdef MEASURE_DOL_READ_LOCBUF + double t_dol_read_locbuf = spu_read_decrementer(); +#endif + int count = 0; + int newLen = 0; + char* buffer = (char*) destination; + int i; + + while (count < len) + { + newLen = (len - count) > MAXELEMENT? MAXELEMENT : len - count; + + while (locBufCount[queue] < newLen) + { + updateLocBuf(queue); + } + + // Can directly read all elements from the end of the buffer + if (locBufStart[queue] + newLen < MAXELEMENT) + { + memcpy(buffer, &(locBuf[queue][locBufStart[queue]]), newLen); + } + + // Has to write from the end and from the beginning of the buffer + else + { + memcpy(&(buffer[count]), &(locBuf[queue][locBufStart[queue]]), MAXELEMENT - locBufStart[queue]); + memcpy(&(buffer[count + MAXELEMENT - locBufStart[queue]]), locBuf[queue], newLen - MAXELEMENT + locBufStart[queue]); + } + + locBufCount[queue] -= newLen; + locBufStart[queue] = (locBufStart[queue] + newLen) % MAXELEMENT; + count += newLen; + } +#ifdef MEASURE_DOL_READ_LOCBUF + t_dol_read_locbuf -= spu_read_decrementer(); + printf("DOL_READ_LOCBUF;%f\n",t_dol_read_locbuf * 1 / MEASURE_CPU); +#endif +} + + +#endif // _PROCESS_FIFO_H_ diff --git a/dol/src/dol/visitor/cbe/lib/ProcessWrapper.h b/dol/src/dol/visitor/cbe/lib/ProcessWrapper.h new file mode 100644 index 0000000..c06075d --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/ProcessWrapper.h @@ -0,0 +1,248 @@ +/**************************************************************** + * Process Wrapper Functions + * Creator: lschor, 2008-10-30 + * Description: General process wrapper file, includes DOL_read and DOL_write as main functions + * + * Revision: + * - 2008-10-30: Created + * - 2008-11-08: Update to Double Buffering + * - 2008-11-15: Added Performance Estimation methods + */ + +#ifndef __PROCESS_WRAPPER_H__ +#define __PROCESS_WRAPPER_H__ + +#include +#include +#include +#include +#include // Used for va_list +#include +#include "lib/malloc_align.h" // Malloc for DMA +#include "lib/free_align.h" // Free for DMA +#include "dol.h" +#include "common.h" +#include "estimation.h" + +//Cell Macros +#define waittag(tag_id) mfc_write_tag_mask(1<wptr))->index[dimension] + +#define CREATEPORTVAR(name) \ + int name + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort(&port, base, number_of_indices, index_range_pairs) + +// Include the FIFO Functions +#include "lib/ProcessFifo.h" + +// Include some help functions +#include "lib/ProcessWrapperHelp.h" + +// Struct for a process +typedef struct _process_wrapper { + char* name; + uint32_t* index; + uint32_t is_detached; + uint32_t* port_id; + uint32_t* port_queue_id; + uint32_t number_of_ports; +} ProcessWrapper; + +// Tag address for the write process +uint32_t tag_id_write = 99; + +// Pointer to the current data buffer +char * resultWrite; + +/** + * Finish a DOL_write process + */ +void finish_DOL_write() +{ + if (tag_id_write != 99) + { + #ifdef MEASURE_DOL_WRITE_FINISH + double t_dol_write_finish = spu_read_decrementer(); + #endif + + // Wait until the write process is finished + waittag(tag_id_write); + mfc_tag_release(tag_id_write); // release tag ID before exiting + _free_align(resultWrite); + // Send a okey about the data to the PPE + spu_write_out_mbox((uint32_t)(SPE_WRITE_SUC << 28)); // stalls mailbox is full. + tag_id_write = 99; + //queueWrite = -1; + + #ifdef MEASURE_DOL_WRITE_FINISH + t_dol_write_finish -= spu_read_decrementer(); + printf("DOL_WRITE_FINISH;%f\n",t_dol_write_finish * 1 / MEASURE_CPU); + #endif + + } +} + +/** + * Read from the FIFO + */ +void DOL_read(void *port, void *buf, int len, DOLProcess *process) +{ +#ifdef MEASURE_DOL_READ + double t_dol_read = spu_read_decrementer(); +#endif + + ProcessWrapper* process_wrapper = (ProcessWrapper*)process->wptr; + int i; + + for (i = 0; i < process_wrapper->number_of_ports; i++) + { + if (process_wrapper->port_id[i] == (int)port) + { + int queue = process_wrapper->port_queue_id[i]; + + finish_DOL_write(); + + // Try to get new data into the local Buffer + updateLocBuf(queue); + + // Read from the Buffer + readLocBuf(queue, buf, len); + + break; + } + } +#ifdef MEASURE_DOL_READ + t_dol_read -= spu_read_decrementer(); + printf("DOL_READ;%f\n",t_dol_read * 1 / MEASURE_CPU); +#endif + +} + + +/** + * Write data into the FIFO + */ +void DOL_write(void *port, void *buf, int len, DOLProcess *process) +{ +#ifdef MEASURE_DOL_WRITE + double t_dol_write = spu_read_decrementer(); +#endif + + ProcessWrapper* process_wrapper = (ProcessWrapper*)process->wptr; + int i; + + for (i = 0; i < process_wrapper->number_of_ports; i++) + { + if (process_wrapper->port_id[i] == (int)port) + { + // This is our queue + int queue = process_wrapper->port_queue_id[i]; + + #ifdef MEASURE_DOL_WRITE_START_DMA + double t_dol_write_start_dma = spu_read_decrementer(); + #endif + + // There has been a writing process. Is it already finished? + finish_DOL_write(); + + // Create the new write process + + // reserve DMA tag ID + if((tag_id_write=mfc_tag_reserve())==MFC_TAG_INVALID){ + printf("SPE: ERROR - can't reserve a tag ID\n"); return; + } + + // Create the message we like to send, format: + // 4 bit: code (total 16 possibilities) + // 12 bit: queue (total 4096 possibilities) + // 16 bit: len (total maximal size of 512 KB which could be send) + uint32_t message = (SPE_WRITE_DEMAND << 28) | (queue << 16) | (len); + + #ifdef MEASURE_DOL_WRITE_HANDSHAKE + double t_dol_write_handshake = spu_read_decrementer(); + #endif + // Demand the PPE for an address + spu_write_out_mbox(message); + + // Copy the data into the right data-alignment + resultWrite = (char *)_malloc_align(roundDMA(len), 4); + memcpy(resultWrite, buf, len); + + // Is there enough free space to write into the queues? + uint32_t messageIn = spu_read_in_mbox(); + uint32_t request = messageIn >> 28; + + while (request != (uint32_t) 1) + { + spu_write_out_mbox(message); + messageIn = spu_read_in_mbox(); + request = messageIn >> 28; + } + + // Wait for the response of the PPE, i.e. the address for the FIFO element + uint32_t ea_mfc_h, ea_mfc_l; + uint64_t ea_mfc; + ea_mfc_h = spu_read_in_mbox(); + ea_mfc_l = spu_read_in_mbox(); + ea_mfc = mfc_hl2ea(ea_mfc_h, ea_mfc_l); + + #ifdef MEASURE_DOL_WRITE_HANDSHAKE + t_dol_write_handshake -= spu_read_decrementer(); + printf("DOL_WRITE_HANDSHAKE;%f\n",t_dol_write_handshake * 1 / MEASURE_CPU); + #endif + + + #ifdef MEASURE_DOL_WRITE_START_DMA + t_dol_write_start_dma -= spu_read_decrementer(); + printf("DOL_WRITE_START_DMA;%f\n",t_dol_write_start_dma * 1 / MEASURE_CPU); + #endif + + #ifdef MEASURE_DOL_WRITE_DMA + double t_dol_write_dma = spu_read_decrementer(); + #endif + + // Write the data to the address we got from the PPE + mfc_put((void *)&(resultWrite[0]), ea_mfc, roundDMA(len), tag_id_write, 0, 0); + + #ifdef MEASURE_DOL_WRITE_DMA + t_dol_write_dma -= spu_read_decrementer(); + printf("DOL_WRITE_DMA;%f\n",t_dol_write_dma * 1 / MEASURE_CPU); + #endif + + + break; + } + } +#ifdef MEASURE_DOL_WRITE + t_dol_write -= spu_read_decrementer(); + printf("DOL_WRITE;%f\n",t_dol_write * 1 / MEASURE_CPU); +#endif +} + +/** + * Detach the DOL Process + */ +void DOL_detach(DOLProcess *process) { + //printf("[%s] DOL_detach.\n", (char*)(((ProcessWrapper*)process->wptr)->name)); + + // Check if all write processes are finished + if (tag_id_write != 99) + { + waittag(tag_id_write); + mfc_tag_release(tag_id_write); // release tag ID before exiting + _free_align(resultWrite); + spu_write_out_mbox((uint32_t)(2 << 28)); // stalls mailbox is full. + } + + uint32_t message = (SPE_DETACH << 28); + spu_write_out_mbox(message); + deinitLocBuf(); + ((ProcessWrapper*)process->wptr)->is_detached = 1; +} + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/ProcessWrapperHelp.h b/dol/src/dol/visitor/cbe/lib/ProcessWrapperHelp.h new file mode 100644 index 0000000..7d2a117 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/ProcessWrapperHelp.h @@ -0,0 +1,100 @@ +/**************************************************************** + * Process Wrapper Help functions + * Creator: lschor, 2008-11-21 + * Description: General process wrapper fucntions + * + * Revision: + * - 2008-11-21: Created + */ + +#ifndef __PROCESS_WRAPPER_HELP_H__ +#define __PROCESS_WRAPPER_HELP_H__ + +#include // Used for va_list + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int getIndex(const char* string, char* tokens, int indexNumber) { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + free(string_copy); + return -1; +} + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +int *createPort(int *port, int base, int number_of_indices, + int index_range_pairs, ...) { + int index[4]; + int range[4]; + int i; + int value; + + va_list marker; + va_start(marker, index_range_pairs); + + value = index_range_pairs; + for (i = 0; i < number_of_indices; i++) { + index[i] = value; + value = va_arg(marker, int); + range[i] = value; + if (i < number_of_indices - 1) { + value = va_arg(marker, int); + } + } + + *port = base; + for (i = 0; i < number_of_indices; i++) { + int j; + int weight = 1; + for (j = 1; j < number_of_indices - i; j ++) { + weight *= range[j]; + } + *port += index[i] * weight; + } + + return port; +} + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/ProcessWrapperPPE.c b/dol/src/dol/visitor/cbe/lib/ProcessWrapperPPE.c new file mode 100644 index 0000000..f94c7ea --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/ProcessWrapperPPE.c @@ -0,0 +1,150 @@ +/**************************************************************** + * Process Wrapper Functions + * Creator: lschor, 2008-10-30 + * Description: General process wrapper file, includes DOL_read and DOL_write as main functions + * + * Revision: + * - 2008-10-30: Created + * - 2008-11-08: Update to Double Buffering + * - 2008-11-15: Added Performance Estimation methods + */ + +#ifndef __PROCESS_WRAPPER_PPE_H__ +#define __PROCESS_WRAPPER_PPE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common_ppu.h" +#include "dol.h" +#include "ProcessWrapperHelp.h" + + +/** + * Read from the FIFO + */ +void DOL_read(void *port, void *buf, int len, DOLProcess *process) +{ + ProcessWrapper* process_wrapper = (ProcessWrapper*)process->wptr; + int i; + + for (i = 0; i < process_wrapper->number_of_ports; i++) + { + if (process_wrapper->port_id[i] == (uint64_t)port) + { + int queue = process_wrapper->port_queue_id[i]; + int i = 0; + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 5; + + + while (process_wrapper->locBufCount[queue] < len) + { + nanosleep(&t, NULL); + }; + + char *buffer = (char *)buf; + + pthread_mutex_lock( &(process_wrapper->mutex[queue]) ); + + // Can directly read all elements from the end of the buffer + if (process_wrapper->locBufStart[queue] + len < process_wrapper->MAXELEMENT) + { + memcpy(buffer, &(process_wrapper->locBuf[queue][process_wrapper->locBufStart[queue]]), len); + } + + // Has to write from the end and from the beginning of the buffer + else + { + memcpy(&(buffer[0]), &(process_wrapper->locBuf[queue][process_wrapper->locBufStart[queue]]), process_wrapper->MAXELEMENT - process_wrapper->locBufStart[queue]); + memcpy(&(buffer[process_wrapper->MAXELEMENT - process_wrapper->locBufStart[queue]]), process_wrapper->locBuf[queue], len - process_wrapper->MAXELEMENT + process_wrapper->locBufStart[queue]); + } + + process_wrapper->locBufCount[queue] -= len; + process_wrapper->locBufStart[queue] = (process_wrapper->locBufStart[queue] + len) % process_wrapper->MAXELEMENT; + + pthread_mutex_unlock( & (process_wrapper->mutex[queue]) ); + + + break; + } + } +} + + +/** + * Write data into the FIFO + */ +void DOL_write(void *port, void *buf, int len, DOLProcess *process) +{ + ProcessWrapper* process_wrapper = (ProcessWrapper*)process->wptr; + int i; + + for (i = 0; i < process_wrapper->number_of_ports; i++) + { + if (process_wrapper->port_id[i] == (uint64_t)port) + { + // This is our queue + int queue = process_wrapper->port_queue_id[i]; + + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 5; + + // Wait if there is not enough space in the fifo + while (process_wrapper->MAXELEMENT - process_wrapper->locBufCount[queue] < len) + { + nanosleep(&t, NULL); + }; + + char *buffer = (char *)buf; + + pthread_mutex_lock( &(process_wrapper->mutex[queue]) ); + + // Has to write all elements at the beginning or write all elements at the end of the buffer + if (process_wrapper->locBufStart[queue] + process_wrapper->locBufCount[queue] > process_wrapper->MAXELEMENT || process_wrapper->locBufStart[queue] + process_wrapper->locBufCount[queue] + len < process_wrapper->MAXELEMENT) + { + memcpy(&(process_wrapper->locBuf[queue][(process_wrapper->locBufStart[queue] + process_wrapper->locBufCount[queue]) % process_wrapper->MAXELEMENT]), buffer, len); + } + + // Otherwise something at the end and something at the beginning + else + { + // At the end of the buffer + memcpy(&(process_wrapper->locBuf[queue][(process_wrapper->locBufStart[queue] + process_wrapper->locBufCount[queue])]), buffer, process_wrapper->MAXELEMENT - process_wrapper->locBufStart[queue] - process_wrapper->locBufCount[queue]); + + // At the beginning + memcpy(process_wrapper->locBuf[queue], &(buffer[process_wrapper->MAXELEMENT - process_wrapper->locBufStart[queue] - process_wrapper->locBufCount[queue]]), len - (process_wrapper->MAXELEMENT - process_wrapper->locBufStart[queue] - process_wrapper->locBufCount[queue])); + } + + process_wrapper->locBufCount[queue]+=len; + + pthread_mutex_unlock( & (process_wrapper->mutex[queue]) ); + + break; + } + } +} + +/** + * Detach the DOL Process + */ +void DOL_detach(DOLProcess *process) { + ProcessWrapper* process_wrapper = (ProcessWrapper*)process->wptr; + printf("[%s] DOL_detach.\n", (char*)(((ProcessWrapper*)process->wptr)->name)); + + pthread_mutex_lock( (process_wrapper->mutexProcessNr) ); + (* process_wrapper->processFinished)++; + pthread_mutex_unlock( (process_wrapper->mutexProcessNr) ); + + ((ProcessWrapper*)process->wptr)->is_detached = 1; +} + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/common.h b/dol/src/dol/visitor/cbe/lib/common.h new file mode 100644 index 0000000..9370673 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/common.h @@ -0,0 +1,59 @@ +/**************************************************************** + * COMMON.H + * Creator: lschor, 2008-10-30 + * Description: Specifies some structs and Constants for the CBE-DOL-Implementation + * + * Revision: + * - 2008-10-30: Created + */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include "dol.h" +#include "lib/constant.h" + +#define SPE_WRITE_DEMAND 0 +#define SPE_READ_DEMAND 1 +#define SPE_WRITE_SUC 2 +#define SPE_READ_SUC 3 +#define SPE_DETACH 4 + +// the context that PPE forward to SPE +typedef struct{ + uint64_t port_id; + uint64_t port_queue_id; + + uint64_t processName; // Address for the name of the process + uint64_t processNameLen; // Len of the process Name + + uint32_t number_of_ports; + uint32_t is_detached; + uint32_t padd[2]; // dummy - for alignment --> It always has to be a multiple of 128 bit! +} parm_context; // aligned to 16B + +typedef struct{ + uint32_t d; + uint32_t padd[31]; //padd to cache line +} status_s; + + +// Round a number ot the right DMA number --> Is used for DMA transfers +uint32_t roundDMA(uint32_t number) +{ + if (number > 16) + return number + 16 - (number % 16); + else if (number > 8) + return 16; + else if (number > 4) + return 8; + else if (number > 2) + return 4; + else if (number > 1) + return 2; + else + return 1; +} + +#endif // _COMMON_H_ + diff --git a/dol/src/dol/visitor/cbe/lib/common_ppu.h b/dol/src/dol/visitor/cbe/lib/common_ppu.h new file mode 100644 index 0000000..7ee1ed0 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/common_ppu.h @@ -0,0 +1,41 @@ +/**************************************************************** + * Common structs for the PPU + * Creator: lschor, 2008-11-21 + * Description: Common structs for the PPU + * + * Revision: + * - 2008-11-21: Created + */ + +#ifndef __COMMON_PPU_H__ +#define __COMMON_PPU_H__ + +//DOL macros +#define GETINDEX(dimension) \ + ((ProcessWrapper*)(p->wptr))->index[dimension] + +#define CREATEPORTVAR(name) \ + int name + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort(&port, base, number_of_indices, index_range_pairs) + + +// Struct for a process +typedef struct _process_wrapper { + char* name; + uint32_t* index; + uint32_t is_detached; + volatile uint32_t* port_id; + volatile uint32_t* port_queue_id; + uint32_t number_of_ports; + char ** locBuf; + int * locBufCount; + int * locBufStart; + int MAXELEMENT; + int * processFinished; + pthread_mutex_t * mutex; + pthread_mutex_t * mutexProcessNr; +} ProcessWrapper; + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/dol.h b/dol/src/dol/visitor/cbe/lib/dol.h new file mode 100644 index 0000000..2838938 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/dol.h @@ -0,0 +1,28 @@ +#ifndef __DOL_H__ +#define __DOL_H__ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//structure for process +struct _process; + +// +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; +} DOLProcess; + +void DOL_read(void *port, void *buf, int len, DOLProcess *process); +void DOL_write(void *port, void *buf, int len, DOLProcess *process); +void DOL_detach(DOLProcess *process); +int getIndex(const char* string, char* tokens, int indexNumber); +int *createPort(int *port, int base, int number_of_indices, int index_range_pairs, ...); + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/estimation.h b/dol/src/dol/visitor/cbe/lib/estimation.h new file mode 100644 index 0000000..667f60b --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/estimation.h @@ -0,0 +1,50 @@ +/**************************************************************** + * Estimation Defintions + * Creator: lschor, 2008-11-15 + * Description: File with includes for time measurements + * Principle: if 1 then this point will be measured, if 0 then this point will not be measured + * + * Revision: + * - 2008-11-15: Created + */ + +// +#ifndef __ESTIMATION_H__ +#define __ESTIMATION_H__ + +// What to measure? +//#define MEASURE 0 // Measure activeted + +#ifdef MEASURE + //#define MEASURE_DOL_READ 0 // Measure DOL READ + //#define MEASURE_DOL_READ_FINISH 0 // Measure FINISH DOL Read + //#define MEASURE_DOL_READ_START_DMA 0 // Measure Time from start until the DMA process has started + //#define MEASURE_DOL_READ_HANDSHAKE 0 // Measure Time of the whole handshake + //#define MEASURE_DOL_READ_DMA 0 // Measure Time of DMA Setup + //#define MEASURE_DOL_READ_LOCBUF 0 // Measure Time for LocBuf read + //#define MEASURE_DOL_READ_DOUBLEBUF 0 // Measure Time for writing into the buffer + + //#define MEASURE_DOL_WRITE 0 // Measure DOL WRITE + //#define MEASURE_DOL_WRITE_FINISH 0 // Measure FINISH DOL Write + //#define MEASURE_DOL_WRITE_START_DMA 0 // Measure Time from start until the DMA process has started + //#define MEASURE_DOL_WRITE_HANDSHAKE 0 // Measure Time of the whole handshake + //#define MEASURE_DOL_WRITE_DMA 0 // Measure Time of DMA Setup + + //#define MEASURE_DOL_FIRE 0 // Measure DOL FIRE + //#define MEASURE_DOL_INIT 0 // Measure DOL INIT + //#define MEASURE_SPE 0 // Measure whole SPE process + + //#define MEASURE_APPLICATION 0 // Measure whole execution time + //#define MEASURE_SET_UP_SPE_THREAD 0 // Measure time to set up the SPE-threads + //#define MEASURE_SPE_WRITE_DEMAND 0 // Measure time of write demand + //#define MEASURE_SPE_READ_DEMAND 0 // Measure time of read demand + //#define MEASURE_SPE_WRITE_SUC 0 // Measure time of write successful + //#define MEASURE_SPE_READ_SUC 0 // Measure time of read successful + +#endif + +// Some constants +#define MEASURE_START 0xFFFFFFFF // Start decrementer at this value +#define MEASURE_CPU 79800000.0 // Timebase PS3 (in Hz) + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/free_align.h b/dol/src/dol/visitor/cbe/lib/free_align.h new file mode 100644 index 0000000..5924871 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/free_align.h @@ -0,0 +1,65 @@ +/* -------------------------------------------------------------- */ +/* (C)Copyright 2001,2006, */ +/* International Business Machines Corporation, */ +/* Sony Computer Entertainment, Incorporated, */ +/* Toshiba Corporation, */ +/* */ +/* All Rights Reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the */ +/* following conditions are met: */ +/* */ +/* - Redistributions of source code must retain the above copyright*/ +/* notice, this list of conditions and the following disclaimer. */ +/* */ +/* - Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* - Neither the name of IBM Corporation nor the names of its */ +/* contributors may be used to endorse or promote products */ +/* derived from this software without specific prior written */ +/* permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND */ +/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR */ +/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ +/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT */ +/* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; */ +/* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) */ +/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN */ +/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR */ +/* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */ +/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* -------------------------------------------------------------- */ +/* PROLOG END TAG zYx */ +#ifndef _FREE_ALIGN_H_ +#define _FREE_ALIGN_H_ 1 + +#include + +/* Function + * + * void free_align(void *ptr) + * + * Description + * The free_align routine frees a memory buffer allocate by the + * malloc_align routine. See malloc_align for complete details. + */ + +static __inline void _free_align(void *ptr) +{ + void * real; + + if (ptr) { + real = *((void **)(ptr)-1); + free(real); + } +} + +#endif /* _FREE_ALIGN_H_ */ diff --git a/dol/src/dol/visitor/cbe/lib/malloc_align.h b/dol/src/dol/visitor/cbe/lib/malloc_align.h new file mode 100644 index 0000000..0a19068 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/malloc_align.h @@ -0,0 +1,105 @@ +/* -------------------------------------------------------------- */ +/* (C)Copyright 2001,2007, */ +/* International Business Machines Corporation, */ +/* Sony Computer Entertainment, Incorporated, */ +/* Toshiba Corporation, */ +/* */ +/* All Rights Reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the */ +/* following conditions are met: */ +/* */ +/* - Redistributions of source code must retain the above copyright*/ +/* notice, this list of conditions and the following disclaimer. */ +/* */ +/* - Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* - Neither the name of IBM Corporation nor the names of its */ +/* contributors may be used to endorse or promote products */ +/* derived from this software without specific prior written */ +/* permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND */ +/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR */ +/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ +/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT */ +/* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; */ +/* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) */ +/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN */ +/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR */ +/* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */ +/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* -------------------------------------------------------------- */ +/* PROLOG END TAG zYx */ + +#ifndef _MALLOC_ALIGN_H_ +#define _MALLOC_ALIGN_H_ 1 + +#include + +/* Function + * + * void * malloc_align(size_t size, unsigned int log2_align) + * + * Description + * The malloc_align routine allocates a memory buffer of + * bytes aligned to the power of 2 alignment specified by . + * For example, malloc_align(4096, 7) will allocate a memory heap + * buffer of 4096 bytes aligned on a 128 byte boundary. + * + * The aligned malloc routine allocates an enlarged buffer + * from the standard memory heap. Space for the real allocated memory + * pointer is reserved on the front of the memory buffer. + * + * ----------------- <--- start of allocated memory + * | pad 0 to | + * |(1< +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "lib/malloc_align.h" +#include "lib/free_align.h" +#include "common.h" +#include "lib/estimation.h" +#include "common_ppu.h" +#include "cbe_mfc.h" + +// Handler for the SPE +extern spe_program_handle_t spu; + +// Program context for the SPEs +volatile parm_context ctx[NUM_SPES] __attribute__ ((aligned(16))); + +// The SPE-program-handler +spe_program_handle_t *program[NUM_SPES]; + +// data structure for running SPE thread +typedef struct spu_data { + spe_context_ptr_t spe_ctx; + pthread_t pthread; + void *argp; +} spu_data_t; + +// Data for the SPEs +spu_data_t data[NUM_SPES]; + +// Struct which stores a fifo entry temporary +struct fifoEntry { + uint64_t ea; + int length; + int queue; +}; + +// Two temporary store elements +struct fifoEntry tmpFifoEntryRead[NUM_SPES]; +struct fifoEntry tmpFifoEntryWrite[NUM_SPES]; + +// Initialize the fifo, we use +const int MAXELEMENT = 262144; +char *locBuf[NUM_FIFO]; +int locBufCount[NUM_FIFO]; +int locBufStart[NUM_FIFO]; + +// Mutex-variables for the read/write process of the Queues +pthread_mutex_t mutex [NUM_FIFO]; +pthread_mutex_t mutexProcessNr = PTHREAD_MUTEX_INITIALIZER; + +// Number of process that have been finished +int processFinished = 0; + + +#endif diff --git a/dol/src/dol/visitor/cbe/lib/ppu_main_workloop.h b/dol/src/dol/visitor/cbe/lib/ppu_main_workloop.h new file mode 100644 index 0000000..874bf32 --- /dev/null +++ b/dol/src/dol/visitor/cbe/lib/ppu_main_workloop.h @@ -0,0 +1,235 @@ +/**************************************************************** + * Main Loop function + * Creator: lschor, 2008-11-21 + * Description: Includes the main loop function for the ppu_main function + * + * Revision: + * - 2008-11-21: Created + */ + +void workloop() +{ + // Buffer to an address for sending to to the PPE + volatile char *dataToWriteP __attribute__ ((aligned(128))); + + // Number of corrent working process + int processNr; + + // General variables that have been used in the main workloop + uint32_t message; + uint32_t request; + uint32_t len; + uint32_t queue; + + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 5; + + while (1) + { + // Goes through all nodes and check if they like to send some data + // - Which node has some information + // - Run through all nodes and if one has some work, do that + for (processNr = 0; processNr < NUM_SPES; processNr++) + { + if (!spe_out_mbox_status(data[processNr].spe_ctx)) continue; + + // Get the data + spe_out_mbox_read(data[processNr].spe_ctx, &message, 1); + + request = message >> 28; + len = (0xffff & message); + queue = (message >> 16) & 0xfff; + + // What type of request are the data? + if (request == SPE_WRITE_DEMAND) // Want an address to write + { + + // If there is not enough space in the queue + if (MAXELEMENT - locBufCount[queue] < len) + { + int message = (0 << 28) | (queue << 16) | locBufCount[queue]; + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING); + } + + // There is enough space in the queue + else + { + #ifdef MEASURE_SPE_WRITE_DEMAND + struct timeval t_ppe_write_demand_start, t_ppe_write_demand_end; + gettimeofday(&t_ppe_write_demand_start,NULL); + #endif + int message = (1 << 28) | (queue << 16) | len; + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING); + + // Create the memory for the element + dataToWriteP = (char *)malloc(roundDMA(len)); + + // Create the address to send + uint64_t ea; + ea = (uint64_t)dataToWriteP; + + // Enter the information into the temporary Buffer + tmpFifoEntryWrite[processNr].ea = ea; + tmpFifoEntryWrite[processNr].length=len; + tmpFifoEntryWrite[processNr].queue = queue; + + // Send the address + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&ea, 2,SPE_MBOX_ANY_NONBLOCKING); + #ifdef MEASURE_SPE_WRITE_DEMAND + gettimeofday(&t_ppe_write_demand_end, NULL); + printf("PPE_WRITE_DEMAND;%f\n",(t_ppe_write_demand_end.tv_sec - t_ppe_write_demand_start.tv_sec) + 0.000001 * (t_ppe_write_demand_end.tv_usec - t_ppe_write_demand_start.tv_usec)); + #endif + } + } + + /***************************************************************************************************************/ + else if (request == SPE_READ_DEMAND) // Want an address to read + { + // If no element is in the fifo queue + if (locBufCount[queue] <= 0) + { + int message = (0 << 28) | (queue << 16) | locBufCount[queue]; + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING); + } + + // Has some elements in the fifo queue + else + { + #ifdef MEASURE_SPE_READ_DEMAND + struct timeval t_ppe_read_demand_start, t_ppe_read_demand_end; + gettimeofday(&t_ppe_read_demand_start,NULL); + #endif + + pthread_mutex_lock( &(mutex[queue]) ); + + len = len > locBufCount[queue] ? locBufCount[queue] : len; + + // Can only send special sizes over the DMA + // 1, 2, 4, 8, 16, ..., 16*x + + if (len >= 16) + len = len - (len % 16); + else if (len >= 8) + len = 8; + else if (len >= 4) + len = 4; + else if (len >= 2) + len = 2; + else + len = 1; + + int message = (1 << 28) | (queue << 16) | len; + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING); + + char *buf = (char *)_malloc_align(sizeof(char) * len, 4); + + // Can directly read all elements from the end of the buffer + if (locBufStart[queue] + len < MAXELEMENT) + { + memcpy(buf, &(locBuf[queue][locBufStart[queue]]), len); + } + + // Has to write from the end and from the beginning of the buffer + else + { + memcpy(&(buf[0]), &(locBuf[queue][locBufStart[queue]]), MAXELEMENT - locBufStart[queue]); + memcpy(&(buf[MAXELEMENT - locBufStart[queue]]), locBuf[queue], len - MAXELEMENT + locBufStart[queue]); + } + + locBufCount[queue] -= len; + locBufStart[queue] = (locBufStart[queue] + len) % MAXELEMENT; + + pthread_mutex_unlock( & (mutex[queue]) ); + + // Create the address to send + uint64_t ea; + ea = (uint64_t)buf; + + // Enter the information into the temporary Buffer + tmpFifoEntryRead[processNr].ea = ea; + tmpFifoEntryRead[processNr].length = len; + tmpFifoEntryRead[processNr].queue = queue; + + spe_in_mbox_write(data[processNr].spe_ctx, (uint32_t*)&(tmpFifoEntryRead[processNr].ea), 2,SPE_MBOX_ANY_NONBLOCKING); + #ifdef MEASURE_SPE_READ_DEMAND + gettimeofday(&t_ppe_read_demand_end, NULL); + printf("PPE;%f\n",(t_ppe_read_demand_end.tv_sec - t_ppe_read_demand_start.tv_sec) + 0.000001 * (t_ppe_read_demand_end.tv_usec - t_ppe_read_demand_start.tv_usec)); + #endif + } + } + + /***************************************************************************************************************/ + else if (request == SPE_WRITE_SUC) // Has finished writing + { + #ifdef MEASURE_SPE_WRITE_SUC + struct timeval t_ppe_write_suc_start, t_ppe_write_suc_end; + gettimeofday(&t_ppe_write_suc_start,NULL); + #endif + // Enter the data into the FIFO + int queueW = tmpFifoEntryWrite[processNr].queue; + int lenW = tmpFifoEntryWrite[processNr].length; + char *bufferW = (char *)(tmpFifoEntryWrite[processNr].ea); + + pthread_mutex_lock( &(mutex[queueW]) ); + + // Has to write all elements at the beginning or write all elements at the end of the buffer + if (locBufStart[queueW] + locBufCount[queueW] > MAXELEMENT || locBufStart[queueW] + locBufCount[queueW] + lenW < MAXELEMENT) + { + memcpy(&(locBuf[queueW][(locBufStart[queueW] + locBufCount[queueW]) % MAXELEMENT]), bufferW, lenW); + } + + // Otherwise something at the end and something at the beginning + else + { + // At the end of the buffer + memcpy(&(locBuf[queueW][(locBufStart[queueW] + locBufCount[queueW])]), bufferW, MAXELEMENT - locBufStart[queueW] - locBufCount[queueW]); + + // At the beginning + memcpy(locBuf[queueW], &(bufferW[MAXELEMENT - locBufStart[queueW] - locBufCount[queueW]]), lenW - (MAXELEMENT - locBufStart[queueW] - locBufCount[queueW])); + } + + locBufCount[queueW]+=lenW; + + pthread_mutex_unlock( & (mutex[queueW]) ); + + // Set the temporary entry to null + #ifdef MEASURE_SPE_WRITE_SUC + gettimeofday(&t_ppe_write_suc_end, NULL); + printf("PPE;%f\n",(t_ppe_write_suc_end.tv_sec - t_ppe_write_suc_start.tv_sec) + 0.000001 * (t_ppe_write_suc_end.tv_usec - t_ppe_write_suc_start.tv_usec)); + #endif + } + + /***************************************************************************************************************/ + else if (request == SPE_READ_SUC) // Has finished reading + { + #ifdef MEASURE_SPE_READ_SUC + struct timeval t_ppe_read_suc_start, t_ppe_read_suc_end; + gettimeofday(&t_ppe_read_suc_start,NULL); + #endif + #ifdef MEASURE_SPE_READ_SUC + gettimeofday(&t_ppe_read_suc_end, NULL); + printf("PPE;%f\n",(t_ppe_read_suc_end.tv_sec - t_ppe_read_suc_start.tv_sec) + 0.000001 * (t_ppe_read_suc_end.tv_usec - t_ppe_read_suc_start.tv_usec)); + #endif + } + + /***************************************************************************************************************/ + else if (request == SPE_DETACH) // A process has finished + { + pthread_mutex_lock( &(mutexProcessNr) ); + processFinished++; + pthread_mutex_unlock( &(mutexProcessNr) ); + } + + /***************************************************************************************************************/ + else // Did nothing + { + nanosleep(&t, NULL); + } + } + + // Check if we can stop the execution + if (processFinished >= NUM_PROCS) break; + } +} + diff --git a/dol/src/dol/visitor/cbe/template/ppu_process_wrapper_template.c b/dol/src/dol/visitor/cbe/template/ppu_process_wrapper_template.c new file mode 100644 index 0000000..a47b3db --- /dev/null +++ b/dol/src/dol/visitor/cbe/template/ppu_process_wrapper_template.c @@ -0,0 +1,68 @@ +/**************************************************************** + * Process Wrapper file for a PPE process + * Creator: lschor, 2008-11-21 + * Description: Wrapper for a specific PPE process + * + * Revision: + * - 2008-11-21: Created + */ + +// Include the standard thread functions +#include +#include +#include +#include +#include +#include +#include + +#include "common_ppu.h" + +// Include of the process --> Because all is mapped to one file, each source file is only allow to appear once +//#include "@PROCESSNAME@.c" + +/* + * The wrapper process + */ +void *@PROCESSNAME@_wrapper( void *ptr ) +{ + // Create the wrapper process + ProcessWrapper *wrapper = (ProcessWrapper *)ptr; + + //initialize the index array + int i; + wrapper->index = (uint32_t *)malloc(4 * sizeof(int)); + for (i = 0; i < 4; i++) + { + wrapper->index[i] = getIndex(wrapper->name, "_", i); + } + + // DOL process + DOLProcess* process; + struct _local_states *state; + + process = (DOLProcess *)malloc(sizeof(DOLProcess)); + state = (struct _local_states *)malloc(sizeof(struct _local_states)); + + memcpy(process, &@PROCESSNAME@, sizeof(DOLProcess)); + memcpy(state, @PROCESSNAME@.local, sizeof(struct _local_states)); + process->local = state; + process->wptr = wrapper; + + // Init the process + process->init(process); + + // Run the process + while (!wrapper->is_detached) + { + process->fire(process); + } + + // Delete all malloc memory + free(state); + free(process); + free(wrapper->name); + free(wrapper); +} + + diff --git a/dol/src/dol/visitor/cbe/template/spu_process_wrapper_template.c b/dol/src/dol/visitor/cbe/template/spu_process_wrapper_template.c new file mode 100644 index 0000000..703518e --- /dev/null +++ b/dol/src/dol/visitor/cbe/template/spu_process_wrapper_template.c @@ -0,0 +1,150 @@ +/**************************************************************** + * Process Wrapper file + * Creator: lschor, 2008-10-30 + * Description: Wrapper for a specific SPE process + * + * Revision: + * - 2008-10-30: Created + * - 2008-11-08: Update to Double Buffering + * - 2008-11-15: Added Performance Estimation methods + */ + +// General includes +#include +extern "C" { + #include +} +#include +#include +#include + +// Include to allocate/free using for DMA transfers +#include "lib/malloc_align.h" +#include "lib/free_align.h" + +// Local includes +#include "common.h" +#include "lib/ProcessWrapper.h" +#include "lib/estimation.h" + +// Context file +static parm_context ctx __attribute__ ((aligned (128))); + +// Include of the process +//#include "@PROCESSNAME@.c" + +// Main application +int main(int speid , uint64_t argp) +{ +#ifdef MEASURE + // Start the decrementer for time measurement + spu_write_decrementer(MEASURE_START); +#endif + +#ifdef MEASURE_SPE + double t_dol_spe = spu_read_decrementer(); +#endif + + // reserve DMA tag ID + uint32_t tag_id; + if((tag_id=mfc_tag_reserve())==MFC_TAG_INVALID){ + printf("SPE: ERROR - can't reserve a tag ID\n"); return 1; + } + + + // Get the context information for the whole system + mfc_get((void*) &ctx, argp, sizeof(ctx), tag_id, 0, 0); + mfc_write_tag_mask(1<number_of_ports = ctx.number_of_ports; + wrapper->port_id = port_id; + wrapper->port_queue_id = port_queue_id; + wrapper->is_detached = 0; + wrapper->name = (char *)_malloc_align((uint32_t)ctx.processNameLen, 4); + mfc_get((void *)wrapper->name, ctx.processName, ctx.processNameLen, tag_id, 0, 0); + waittag(tag_id); + + + //initialize the index array + int i; + wrapper->index = (uint32_t *)malloc(4 * sizeof(int)); + for (i = 0; i < 4; i++) + { + wrapper->index[i] = getIndex(wrapper->name, "_", i); + } + + // Init the queues for local buffering in the LS + // Only the queues we really need + initQueues(); + + // DOL process + DOLProcess* process; + struct _local_states *state; + + process = (DOLProcess *)malloc(sizeof(DOLProcess)); + state = (struct _local_states *)malloc(sizeof(struct _local_states)); + + memcpy(process, &@PROCESSNAME@, sizeof(DOLProcess)); + memcpy(state, @PROCESSNAME@.local, sizeof(struct _local_states)); + process->local = state; + process->wptr = wrapper; + + // Init the process +#ifdef MEASURE_DOL_INIT + double t_dol_init = spu_read_decrementer(); +#endif + process->init(process); +#ifdef MEASURE_DOL_INIT + t_dol_init -= spu_read_decrementer(); + printf("DOL_INIT;%f\n",t_dol_init * 1 / MEASURE_CPU); +#endif + + // Run the process + while (!wrapper->is_detached) + { + #ifdef MEASURE_DOL_FIRE + double t_dol_fire = spu_read_decrementer(); + #endif + + process->fire(process); + + #ifdef MEASURE_DOL_FIRE + t_dol_fire -= spu_read_decrementer(); + printf("DOL_FIRE;%f\n",t_dol_fire * 1 / MEASURE_CPU); + #endif + } + + // Delete all malloc memory + free(state); + free(process); + _free_align(wrapper->name); + free(wrapper); + + // release tag ID before exiting + mfc_tag_release(tag_id); + + + // Inform the main process that we have finished! + spu_write_out_mbox((uint32_t)(4)); + +#ifdef MEASURE_SPE + t_dol_spe -= spu_read_decrementer(); + printf("DOL_SPE_@PROCESSNAME@;%f\n",t_dol_spe * 1 / MEASURE_CPU); +#endif + + return 0; +} + + diff --git a/dol/src/dol/visitor/cell/CellBuildFileVisitor.java b/dol/src/dol/visitor/cell/CellBuildFileVisitor.java new file mode 100644 index 0000000..31daef5 --- /dev/null +++ b/dol/src/dol/visitor/cell/CellBuildFileVisitor.java @@ -0,0 +1,110 @@ +package dol.visitor.cell; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate the build file + * for the Application on the CBE (i.e. a bash file for the cell + * + * @author lschor, 2008-10-30 + * + * Revision: + * 2008-10-30: Updated the file for the CBE + * 2008-11-08: Add double buffering + */ +public class CellBuildFileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir + * path of the Makefile + */ + public CellBuildFileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param pn + * process network + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "pncbe.sh"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + // General information / notes and commands + ps.println("#!/bin/bash"); + ps.println("clear"); + ps.println(); + ps + .println("# Bash file to run the DOL application on the Cell processor"); + ps + .println("# Start the Cell-Simulator and enter the following commands: "); + ps + .println("# callthru source /pncbe.sh > pncbe.sh"); + ps + .println("# for example: callthru source /opt/cell/sdk/src/tutorial/pn_test/pncbe.sh > pncbe.sh"); + ps.println("# chmod +x pncbe.sh"); + ps.println(); + ps + .println("# To run the DOL application, use the following command: "); + ps.println("# ./pncbe.sh"); + ps.println(); + ps.println("# Folder in which the application is stored"); + ps.println("FOLDER=/opt/cell/sdk/src/tutorial/pn_test"); + ps.println(); + + // Go through each process and copy its data to the Cell + String subdirectory = ""; + String applicationName = ""; + Vector pList = new Vector(); + + for (Process process : pn.getProcessList()) { + String basename = process.getBasename(); + if (!pList.contains(basename) && process.getNumOfInports() > 0 && process.getNumOfOutports() > 0) { + subdirectory = "spu_" + basename; + applicationName = subdirectory + "/spu_" + + basename + "_wrapper"; + + ps.println("# " + basename); + ps.println("if [ ! -d " + subdirectory + " ]; then"); + ps.println(" mkdir " + subdirectory); + ps.println("fi;"); + ps.println("callthru source $FOLDER/" + applicationName + + " > " + applicationName); + ps.println("chmod +x " + applicationName); + ps.println(); + pList.add(basename); + } + } + + // Load also the main application to the CBE + ps.println("# Main program"); + ps.println("callthru source $FOLDER/ppu_main > ppu_main"); + ps.println("chmod +x ppu_main"); + ps.println(); + + // Run the main program + ps.println("# run the main program"); + ps.println("./ppu_main"); + } catch (Exception e) { + System.out.println("CbeMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + +} diff --git a/dol/src/dol/visitor/cell/CellConstantVisitor.java b/dol/src/dol/visitor/cell/CellConstantVisitor.java new file mode 100644 index 0000000..ce7e2a1 --- /dev/null +++ b/dol/src/dol/visitor/cell/CellConstantVisitor.java @@ -0,0 +1,128 @@ +package dol.visitor.cell; + +import java.io.FileOutputStream; +import java.io.OutputStream; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; +import dol.main.UserInterface; +import dol.util.CodePrintStream; + +/** + * This class is a class for a constant file + * + * @author lschor, 2008-11-08 + * + * Revision: + * 2008-11-08: Created + */ +public class CellConstantVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public CellConstantVisitor(String dir, CellMapping mapping) { + _dir = dir; + _mapping = mapping; + } + + /** + * Visit process network. + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + _ui = UserInterface.getInstance(); + String filename = _dir + _delimiter + "lib" + _delimiter + "constant.h"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + int numSpes = 0; + + for (Process p : x.getProcessList()) { + if (p.getNumOfInports() > 0 && p.getNumOfOutports() > 0) + numSpes++; + } + + //create header section + _mainPS.println("// ========================"); + _mainPS.println("// constant.h file"); + _mainPS.println("// ========================"); + + // Includes + _mainPS.println("#include "); + + // Define the number of FIFO queues and the number of processes + _mainPS.println("#ifndef _CONSTANT_H_"); + _mainPS.println("#define _CONSTANT_H_"); + _mainPS.println(""); + _mainPS.println("#define NUM_PROCS " + x.getProcessList().size() + + " // total number of processes"); + _mainPS.println("#define NUM_PROCS_SPU " + _mapping.getNrSPUProcess() + + " // number of processes to map on the SPU"); + _mainPS.println("#define NUM_PROCS_PPU " + _mapping.getNrPPUProcess() + + " // number of processes to map to the PPU"); + _mainPS.println("#define NUM_SPES " + _mapping.getNrSPE() + + " // number of SPE's to be used (PS3 = 6)"); + _mainPS.println("#define NUM_FIFO " + x.getChannelList().size() + + " // number of FIFOs we use"); + _mainPS.println("#define FIFO_SIZE_FACTOR 10 " + + " // factor to increase the IN-Channels of the PPE"); + _mainPS.println("#define ALIGNMENT_FACTOR 7 " + + " // alignment factor for the DMA transfers (at least 4, " + + "7 for optimal performance)"); + _mainPS.println("#define ALIGNMENT_FACTOR_POWER2 128"); + _mainPS.println("#define BLOCKED_MAX_NR 5 " + + " // number of times a process should wait until " + + "resending a request"); + _mainPS.println("#define STORE_REQUESTS " + + " // defines if one would like to store requests which" + + "cannot be worked out currently (see also FastCommunication.cpp)"); + _mainPS.println(""); + _mainPS.print("static unsigned long int FIFO_SIZE[NUM_FIFO] = {"); + for (int indx = 0; indx < x.getChannelList().size(); indx++) { + int size = x.getChannelList().elementAt(indx).getSize(); + if (x.getChannelList().elementAt(indx).getTokenSize() != 0) { + size *= x.getChannelList().elementAt(indx).getTokenSize(); + } + if(indx == x.getChannelList().size() - 1) { + _mainPS.print(size); + } else { + _mainPS.print(size + ", "); + } + } + + _mainPS.println("}; // Size of the FIFOs"); + _mainPS.println(""); + _mainPS.println("#endif // _CONSTANT_H_ "); + } + catch (Exception e) { + System.out.println("CellModuleVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param x process that needs to be processed + */ + public void visitComponent(Process x) { + } + + /** + * + * @param x channel that needs to be processed + */ + public void visitComponent(Channel x) { + } + + protected CellMapping _mapping; + protected CodePrintStream _mainPS = null; + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/cell/CellMakefileVisitor.java b/dol/src/dol/visitor/cell/CellMakefileVisitor.java new file mode 100644 index 0000000..8c7e22e --- /dev/null +++ b/dol/src/dol/visitor/cell/CellMakefileVisitor.java @@ -0,0 +1,130 @@ +package dol.visitor.cell; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.pn.Configuration; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate a CBE package + * Makefile. + */ +public class CellMakefileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir + * path of the Makefile + */ + public CellMakefileVisitor(String dir, CellMapping mapping) { + _dir = dir; + _mapping = mapping; + } + + /** + * Create a Makefile for the given process network. + * + * @param pn + * process network + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("####################"); + ps.println("# Main Makefile for the DOL application on the CBE"); + ps.println("####################"); + ps.println(); + ps.println("# Subdirectories"); + + + // Subdirectories of baseprocesses on the SPU + String subdirectories = ""; + Vector pList = new Vector(); + for (Process process : _mapping.getAllSpuBaseProcess()) { + String basename = process.getBasename(); + subdirectories += "spu_" + basename + " "; + } + + // Directory for the SPU OS'es (only if there are really processes on the SPE! + if (_mapping.getNrSPUProcess() > 0) + subdirectories += "spu"; + + String srcdirectories = ""; + + // Subdirectory of baseprocesses on the PPU + for (Process process : _mapping.getAllPPUBaseProcess()) { + String basename = process.getBasename(); + srcdirectories += " $(wildcard ppu_" + basename + "/*.cpp)"; + } + + // Directory for the PPU OS'es + srcdirectories += " $(wildcard ppu/*.cpp)"; + + String linkList = ""; + String srcList = ""; + for (Process process : _mapping.getAllPPUBaseProcess()) { + String basename = process.getBasename(); + linkList += "ppu_" + basename + "/ppu_" + basename + ".o "; + srcList += "$(wildcard ppu_" + basename + "/*.cpp) "; + } + pList.clear(); + + ps.println("DIRS := " + subdirectories); + ps.println(""); + + ps.println("# General definitions:"); + ps.println("CC = ppu-g++"); + ps.println("CCFLAGS = -ftree-vectorize -O3 -maltivec -funroll-loops -mabi=altivec -mcpu=cell"); + ps.println("COMPILE = $(CC) $(CCFLAGS) -c"); + ps.print("LINK = $(CC) -lspe2 -lpthread "); + for (Configuration conf : pn.getCfgList()) { + if (conf.getName().equals("DYNAMIC_LINK")) + ps.print(conf.getValue() + " "); + } + ps.println(""); + + ps.println("CBE_INCLUDE = /opt/cell/sdk/usr/include"); + ps.println("LIB_INC = -I lib -I lib/ppu -I lib/pt -I . -I $(CBE_INCLUDE)"); + ps.println("RM = rm"); + ps.println("ECHO = echo"); + ps.println("EXE = sc_application"); + ps.println(""); + ps.println("src := $(wildcard lib/ppu/*.cpp) $(wildcard *.cpp)" + srcdirectories); + ps.println("srcAll := $(wildcard lib/ppu/*.cpp) $(wildcard *.cpp) " + srcList); + ps.println("srcspu := $(wildcard lib/spu/*.cpp) "); + ps.println("obj = $(src:.cpp=.o)"); + ps.println("objspu = $(srcspu:.cpp=.o)"); + ps.println(""); + ps.println("$(EXE): $(obj) $(srcAll)"); + ps.println("\tfor d in $(DIRS); do (cd $$d; $(MAKE) ); done"); + ps.println("\t$(LINK) -o $(EXE) $(obj)"); + ps.println(""); + ps.println("%.o :"); + ps.println("\t$(COMPILE) -o $(*D)/$(*F).o $(*D)/$(*F).cpp $(LIB_INC)"); + ps.println(""); + ps.println("clean:"); + ps.println("\tfor d in $(DIRS); do (cd $$d; $(MAKE) clean ); done"); + ps.println("\trm $(objspu)"); + ps.println("\trm $(obj) "); + ps.println("\trm $(EXE)"); + ps.println(""); + + } catch (Exception e) { + System.out.println("CbeMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected CellMapping _mapping; +} diff --git a/dol/src/dol/visitor/cell/CellMapping.java b/dol/src/dol/visitor/cell/CellMapping.java new file mode 100644 index 0000000..d5421c5 --- /dev/null +++ b/dol/src/dol/visitor/cell/CellMapping.java @@ -0,0 +1,324 @@ +package dol.visitor.cell; + +import java.util.ArrayList; +import java.util.Vector; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.main.UserInterface; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; + +public class CellMapping { + + /** + * Constructor + * @param pn + */ + CellMapping(ProcessNetwork pn, String mapping, boolean ppu, int nrSPU) { + _pn = pn; + + _maxSPU = nrSPU; + + // Select the mapping + + // Structural mapping + if (mapping.compareTo("structural") == 0) { + System.out.println("Cell: use structural mapping"); + structuralMapping(pn, ppu); + } + + // Random mapping + else if (mapping.compareTo("random") == 0) { + System.out.println("Cell: use random mapping"); + randomMapping(pn, ppu); + } + + // Predefined + else if (mapping.compareTo("predefined") == 0) { + System.out.println("Cell: Use predefined mapping."); + System.out.println(" All other parameters are ignored."); + predefinedMapping(pn); + + _maxSPU = _SPUList.size(); + ppu = this._PPU.size() == 0 ? false : true; + } + + // All to PPU + else if (mapping.compareTo("ppu") == 0) { + System.out.println("Cell: Map all processes to the PPU"); + allPPUMapping(pn); + } + + // Default mapping + else { + System.out.println("WARNING: Cell: use default mapping"); + structuralMapping(pn, ppu); + } + + System.out.println("Cell: Nr of SPE is " + _maxSPU); + + if (ppu) { + System.out.println("Cell: Mapped some processes to the PPE"); + } + } + + /** + * Returns a list which process is mapped to which spu + * + * @return the process list + */ + public ArrayList> getSPUList() { + return _SPUList; + } + + /** + * Returns list of all processes which are mapped to an spu + */ + public Vector getAllSpuProcess() { + Vector processList = new Vector(); + + for (Vector spu : _SPUList) { + for (Process p : spu) { + processList.add(p); + } + } + return processList; + } + + /** + * Returns a list of all base processes which occur on the spu's + */ + public Vector getAllSpuBaseProcess() { + Vector pList = new Vector(); + Vector pListName = new Vector(); + for (Process process : getAllSpuProcess()) { + String basename = process.getBasename(); + if (!pListName.contains(basename)) { + pList.add(process); + pListName.add(basename); + } + } + return pList; + } + + /** + * Returns a list of all processes which are mapped to the ppu + */ + public Vector getPPU() { + return _PPU; + } + + /** + * Returns a list of all base processes which occur on the ppu + */ + public Vector getAllPPUBaseProcess() { + Vector pList = new Vector(); + Vector pListName = new Vector(); + for (Process process : _PPU) { + String basename = process.getBasename(); + if (!pListName.contains(basename)) { + pList.add(process); + pListName.add(basename); + } + } + return pList; + } + + /** + * Return the number of processes which are mapped to the ppu + */ + public int getNrPPUProcess() { + return _PPU.size(); + } + + /** + * Returns the number of processes which are mapped to the SPU + */ + public int getNrSPUProcess() { + return getAllSpuProcess().size(); + } + + /** + * Returns the number of SPU which are used + */ + public int getNrSPE() { + return _SPUList.size(); + } + + /** + * Performs a random mapping of the processes + * + * @param x + * the process network + * @param ppu_choose + * true if the sinks and sources should be mapped to the PPU + */ + protected void randomMapping(ProcessNetwork x, boolean ppu_choose) { + ArrayList> spu = new ArrayList>(); + Vector ppu = new Vector(); + + int addSPU = 0; + for (Process p : x.getProcessList()) { + // A process to map to the PPU + if ((p.getNumOfInports() == 0 || p.getNumOfOutports() == 0) + && ppu_choose) { + ppu.add(p); + } + + // A process to map to the SPU + else { + // As long as there is a free SPU, add the process to this SPU + if (addSPU < _maxSPU) { + Vector pList = new Vector(); + pList.add(p); + spu.add(pList); + } + + // Have to add the process to an already used SPU + else { + spu.get(addSPU % _maxSPU).add(p); + } + + addSPU++; + } + } + + _SPUList = spu; + _PPU = ppu; + } + + /** + * Performs a structural mapping of the processes. That means that the + * function tries to keep the structural order of the process network. The + * order is given depending on the occurrence of the process in the + * structure file. + * + * @param x + * the process network + * @param ppu_choose + * true if the sinks and sources should be mapped to the PPU + */ + protected void structuralMapping(ProcessNetwork x, boolean ppu_choose) { + ArrayList> spu = new ArrayList>(); + Vector ppu = new Vector(); + + Vector spu_temp = new Vector(); + + // Each process is allocated to the PPE or the SPE (as general) + for (Process p : x.getProcessList()) { + // A process to map to the PPU + if ((p.getNumOfInports() == 0 || p.getNumOfOutports() == 0) + && ppu_choose) { + ppu.add(p); + } + + // A process to map to the SPU + else { + spu_temp.add(p); + } + } + + System.out.println("Total processes: " + spu_temp.size()); + + // Do the structural mapping for all the SPE processes + int nrSPUProcess = spu_temp.size(); + for (int i = 0; i < _maxSPU; i++) { + + if (spu_temp.size() <= 0) + break; + + Vector pList = new Vector(); + for (int j = 0; j < Math.ceil(((double) (nrSPUProcess - i)) + / _maxSPU); j++) { + pList.add(spu_temp.remove(0)); + } + + spu.add(pList); + } + + System.out.println("End Total processes: " + spu_temp.size()); + + _SPUList = spu; + _PPU = ppu; + } + + /** + * Maps the processes depending on the architectural and mapping files + * + * @param x + */ + protected void predefinedMapping(ProcessNetwork x) { + // The SPUs + ArrayList> spu = new ArrayList>(); + + // The PPU + Vector ppu = new Vector(); + + ArchiXmlParser archParser = new ArchiXmlParser(); + //System.out.println(_ui.getPlatformFileName()); + Architecture arch = archParser.doParse(_ui.getPlatformFileName()); + MapXmlParser mappingParser = new MapXmlParser(x, arch); + Mapping mapping = mappingParser.doParse(_ui.getMappingFileName()); + + // Go through all process and check where to add + for (Process p : x.getProcessList()) { + for (ComputationBinding b : mapping.getCompBindList()) { + if (b.getProcess().getName().equals(p.getName())) { + // Mapping to PPU + if (b.getProcessor().getName().equals("ppu")) { + System.out.println("Mapped process " + p.getName() + + " to the PPU"); + ppu.add(p); + } + // Maps to one of the SPU, they are written in the format + // spu_0, spu_1, ... + else { + int index = Integer.valueOf(b.getProcessor() + .getName().replaceAll(".*_", "")); + + while (spu.size() <= index) { + Vector spu_temp = new Vector(); + spu.add(spu_temp); + } + + spu.get(index).add(p); + System.out.println("Mapped process " + p.getName() + + " to the SPU_" + index); + } + } + } + } + + _SPUList = spu; + _PPU = ppu; + } + + + /** + * Performs a mapping of all processes to the PPU. + * + * @param x the process network + */ + protected void allPPUMapping(ProcessNetwork x) { + ArrayList> spu = new ArrayList>(); + Vector ppu = new Vector(); + + // Each process is allocated to the PPE or the SPE (as general) + for (Process p : x.getProcessList()) { + ppu.add(p); + } + + _SPUList = spu; + _PPU = ppu; + } + + protected ArrayList> _SPUList = null; + protected Vector _PPU = null; + protected ProcessNetwork _pn; + protected int _maxSPU = 6; + protected UserInterface _ui = UserInterface.getInstance(); +} diff --git a/dol/src/dol/visitor/cell/CellModuleVisitor.java b/dol/src/dol/visitor/cell/CellModuleVisitor.java new file mode 100644 index 0000000..10c700b --- /dev/null +++ b/dol/src/dol/visitor/cell/CellModuleVisitor.java @@ -0,0 +1,386 @@ +package dol.visitor.cell; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; +import dol.main.UserInterface; +import dol.util.CodePrintStream; + +/** + * This class is a class for a visitor that is used to generate the main + * program. + * + * @author lschor, 2008-10-30 + * + * Remarks: Based on a original file from khuang for rtems + * + * Revision: 2008-10-30: Updated the file for the CBE 2008-11-08: Add + * double buffering 2008-11-16: Add new fifo implementation and defines + * for measurement 2008-11-21: Sink/Source do not run on the SPE, but on + * the PPE (as Linux thread) 2009-03-17: Added Protothread Support + * 2009-04-07: Several modifications, so that one only writes if there + * is enough memory, do not allocate all queues on PPU + */ +public class CellModuleVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir + * path of this file + */ + public CellModuleVisitor(String dir, HashMap portMap, + CellMapping mapping) { + _dir = dir; + _portMap = portMap; + _mapping = mapping; + } + + /** + * Visit process network. + * + * @param x + * process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + // Create in a first step the different SPU OS Layers + ArrayList> spuList = _mapping.getSPUList(); + Vector ppu = _mapping.getPPU(); + + // Create the main file for the PPU + _ui = UserInterface.getInstance(); + String filename = _dir + _delimiter + "ppu_main.cpp"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + // create header section + _mainPS.println("// ========================"); + _mainPS.println("// ppu_main.cpp file"); + _mainPS.println("// ========================"); + _mainPS.println(""); + + // Includes + _mainPS.println("#include \"ppu_main.h\""); + _mainPS.println(""); + + // Function to create and run one SPE thread + _mainPS.println("// create and run one SPE thread"); + _mainPS.println("void *spu_pthread(void *arg) {"); + _mainPS.println(" spu_data_t *datp = (spu_data_t *)arg;"); + _mainPS.println(" uint32_t entry = SPE_DEFAULT_ENTRY;"); + _mainPS.println(" printf(\")PPE: spe thread start run\\n\" );"); + _mainPS.println(" if(spe_context_run(datp->spe_ctx,&entry,0,datp->argp,NULL,NULL)<0){"); + _mainPS.println(" perror (\"Failed running context\"); exit (1);"); + _mainPS.println(" }"); + _mainPS.println(" printf(\")PPE: spe thread finish run\\n\");"); + _mainPS.println(" pthread_exit(NULL);"); + _mainPS.println("}"); + _mainPS.println(""); + + // Declaration of the Header function for the PPE-Wrappers + /* + * for (Process p : ppu) { _mainPS.println("void *"+ p.getBasename() + * + "_wrapper( void *ptr );"); } + */ + _mainPS.println("void *ppu( void *ptr );"); + _mainPS.println(); + + // Create the port_id and the port_queue_id arrays to send over the + // DMA + for (Process process : _mapping.getAllSpuProcess()) { + String processName = process.getName(); + _mainPS.println("volatile char " + processName + + "_name[256] __attribute__ ((aligned(16)));"); + } + _mainPS.println(); + + // Go through all SPEs + for (Vector spu : spuList) { + _mainPS.println("volatile uint64_t spu_" + + spuList.indexOf(spu) + "[" + spu.size() + + "] __attribute__ ((aligned(16)));"); + } + _mainPS.println(); + _mainPS.println("// The input queue of a process is always on the SPU of the receiver, negative means that the queue is on the PPE"); + _mainPS.println("volatile int32_t queueOnSPU [NUM_FIFO] __attribute__ ((aligned(16)));"); + _mainPS.println(); + _mainPS.println(" // The queue comes from this SPU"); + _mainPS.println("volatile int32_t queueFromSPU [NUM_FIFO] __attribute__ ((aligned(16)));"); + _mainPS.println(); + _mainPS.println(" volatile uint64_t ea_ls_base[NUM_SPES] __attribute__ ((aligned(16)));"); + _mainPS.println("volatile uint64_t context_addr[NUM_SPES] __attribute__ ((aligned(16)));"); + _mainPS.println("volatile spe_spu_control_area_t* mfc_ctl[NUM_SPES] __attribute__ ((aligned(16)));"); + _mainPS.println("volatile uint64_t fifoTails[NUM_FIFO] __attribute__ ((aligned(16)));"); + _mainPS.println(); + + // Create the main function + _mainPS.println("int main()"); + _mainPS.println("{"); + + // List with all SPU to be open + _mainPS.println(" char spe_names[NUM_SPES][60] = {"); + for (Vector spu : spuList) { + _mainPS.println(" \"spu/spu_os_" + spuList.indexOf(spu) + + "\","); + } + _mainPS.println(" };"); + + _mainPS.println(); + + for (Channel c : x.getChannelList()) { + // Search for the origin spe + int origin = -1; + + for (Vector spu : spuList) { + + if (spu.contains(c.getOrigin())) { + origin = spuList.indexOf(spu); + break; + } + } + + // Origin on the PPU + if (origin == -1) { + origin = (-1) * (ppu.indexOf(c.getOrigin()) + 1); + } + + _mainPS.println(" queueFromSPU[" + + x.getChannelList().indexOf(c) + "] = " + origin + + ";"); + } + + _mainPS.println(); + + for (Channel c : x.getChannelList()) { + // Search for the origin spe + int origin = -1; + + for (Vector spu : spuList) { + if (spu.contains(c.getTarget())) { + origin = spuList.indexOf(spu); + break; + } + } + + // Origin on the PPU + if (origin == -1) { + origin = (-1) * (ppu.indexOf(c.getTarget()) + 1); + } + + _mainPS.println(" queueOnSPU[" + + x.getChannelList().indexOf(c) + "] = " + origin + + ";"); + } + + _mainPS.println(); + + // connect ports to channels + HashMap channel_map = new HashMap(); + + int j = 0; + for (Channel c : x.getChannelList()) { + channel_map.put(c, j++); + } + + // Add to each process the ports and the queues + _mainPS + .println(" //Add to each process the ports and the queues"); + j = 0; + + // SPU Process + for (Process process : _mapping.getAllSpuProcess()) { + String processName = process.getName(); + _mainPS.println(" ctx_proc[" + j + "]" + + ".is_detached = 0;"); + _mainPS.println(" strcpy((char *)" + processName + + "_name, " + "\"" + processName + "\");"); + _mainPS.println(" ctx_proc[" + j + "]" + + ".processName = (uint64_t) " + processName + + "_name;"); + _mainPS.println(" ctx_proc[" + j + "]" + + ".processNameLen = ((strlen((char *)" + + processName + "_name) + 15) & ~15);"); + _mainPS.println(); + j++; + } + + _mainPS.println(" // Mapping tasks -> What to map to which SPE?"); + _mainPS.println(" // Example: Square 0-2 to SPE 0 AND Square 3-5 to SPE 1"); + j = 0; + for (Process process : _mapping.getAllSpuProcess()) { + for (Vector spu : spuList) { + if (spu.contains(process)) { + _mainPS.println(" spu_" + spuList.indexOf(spu) + + "[" + spu.indexOf(process) + + "] = (uint64_t) &(ctx_proc[" + j + + "]);"); + j++; + break; + } + } + } + + for (Vector spu : spuList) { + _mainPS.println(" ctx_spu[" + spuList.indexOf(spu) + + "].procContents = (uint64_t) spu_" + + spuList.indexOf(spu) + ";"); + _mainPS.println(" ctx_spu[" + spuList.indexOf(spu) + + "].procContentsLen = " + spu.size() + ";"); + } + + _mainPS.println(); + + // Init the SPE control structure + _mainPS.println(" //Initiate SPEs control structure"); + _mainPS.println(" int num = 0; "); + _mainPS.println(" for( num=0; num variable store the executable name"); + _mainPS.println(" // - Load SPEs objects into SPE context local store"); + _mainPS.println(" for( num=0; numprocContentsAll = (uint64_t *) context_addr;"); + _mainPS.println(" ppu_Process_Wrapper->queueFromSPU = (int32_t *)queueFromSPU;"); + _mainPS.println(" ppu_Process_Wrapper->queueOnSPU = (int32_t *)queueOnSPU;"); + _mainPS.println(" ppu_Process_Wrapper->ea_ls_base = (uint64_t *)ea_ls_base;"); + _mainPS.println(" ppu_Process_Wrapper->fifoTails = (uint64_t *) fifoTails;"); + _mainPS.println(" "); + + _mainPS.println(" // create SPE pthreads"); + _mainPS.println(" for( num=0; num 16) + return number + 16 - (number % 16); + else if (number > 8) + return 16; + else if (number > 4) + return 8; + else if (number > 2) + return 4; + else if (number > 1) + return 2; + else + return 1; + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected HashMap _portMap; + protected CellMapping _mapping; + +} diff --git a/dol/src/dol/visitor/cell/CellPPEVisitor.java b/dol/src/dol/visitor/cell/CellPPEVisitor.java new file mode 100644 index 0000000..e283fa6 --- /dev/null +++ b/dol/src/dol/visitor/cell/CellPPEVisitor.java @@ -0,0 +1,421 @@ +package dol.visitor.cell; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.StringTokenizer; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +public class CellPPEVisitor extends PNVisitor { + /** + * Constructor. + * + * @param dir + * path of this file + */ + public CellPPEVisitor(String dir, CellMapping mapping) { + _dir = dir; + _mapping = mapping; + } + + /** + * Visit process network. + * + * @param x + * process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + // Create a directory for the OS Layer: + _ppuDir = _dir + _delimiter + "ppu"; + File dir = new File(_ppuDir); + dir.mkdirs(); + + // Copy the header file to this directory + // Some library files must be copied to the main directory + (new File(_dir + _delimiter + "lib" + _delimiter + "ppu_os.h")) + .renameTo(new File(dir.getPath() + _delimiter + + "spu_os.h")); + + createOSLayer(x, _mapping.getPPU()); + + createMakefilePPU(); + } catch (Exception e) { + System.out.println("CellPPEVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates the OS layer for one SPU + * + * @param spu + * List of all processes a SPU gets + * @param index + * index of the SPU in the SPU list + */ + protected void createOSLayer(ProcessNetwork x, Vector ppu) { + try { + // Create the filename for the new layer + String filename = _ppuDir + _delimiter + "ppu_os.cpp"; + + // PrintStream for writing to the file + OutputStream file = new FileOutputStream(filename); + CodePrintStream _code = new CodePrintStream(file); + + _code = new CodePrintStream(file); + + _code.printPrefixln("#include \"pt.h\""); + _code.printPrefixln("#include \"Fifo.h\""); + _code.printPrefixln("#include \"WindowedFifo.h\""); + _code.printPrefixln("#include \"FastCommunication.h\""); + _code.printPrefixln("#include \"common_ppu.h\""); + _code.printPrefixln("#include \"common.h\""); + for (Process p : ppu) { + _code.printPrefixln("#include \".." + _delimiter + "ppu_" + + p.getBasename() + _delimiter + p.getBasename() + + "Wrapper.h\""); + } + + _code.println(); + _code.printPrefixln("void *ppu( void *ptr )"); + _code.printLeftBracket(); + + // instantiate channels + Vector channelList = new Vector(); + int indx = 0; + for (Channel c : x.getChannelList()) { + if (ppu.contains(c.getOrigin()) + || ppu.contains(c.getTarget())) { + channelList.add(c); + + if (c.getType().equals("fifo")) { + // This is a IN-Channel --> Add an additional Factor to increase the speed + if (ppu.contains(c.getTarget()) && !ppu.contains(c.getOrigin())) { + _code.printPrefixln("Fifo " + c.getName() + + "(FIFO_SIZE[" + indx +"] * FIFO_SIZE_FACTOR);"); + // OUT-Channel --> Do not add any factor as we like to stall sometimes + } else { + _code.printPrefixln("Fifo " + c.getName() + + "(FIFO_SIZE[" + indx + "]);"); + } + } else if (c.getType().equals("wfifo")) { + _code.printPrefixln("WindowedFifo " + c.getName() + + "(FIFO_SIZE[" + indx + "]);"); + } + } + indx++; + } + _code.println(); + + // instantiate processes + for (Process p : ppu) { + _code.printPrefix("int " + p.getName() + "Indices[] = { "); + Vector iteratorIndex = p.getIteratorIndices(); + if (iteratorIndex.size() < 4) { + while (iteratorIndex.size() < 4) { + iteratorIndex.add(-1); + } + } else if (iteratorIndex.size() > 4) { + new RuntimeException("Error: Currently not more than " + + "4 iterator dimensions are supported." + + "Consider revising " + p.getBasename() + "."); + } + for (int i = 0; i < 4; i++) { + if (i < 3) { + _code.print(iteratorIndex.elementAt(i) + ", "); + } else { + _code.println(iteratorIndex.elementAt(i) + " };"); + } + } + _code.println(); + _code.printPrefixln(p.getBasename() + "Wrapper *" + p.getName() + ";"); + _code.printPrefixln("try { " + + p.getName() + " = new " + p.getBasename() + + "Wrapper(\"" + p.getName() + "\", " + + p.getName() + "Indices); }"); + _code.println(" catch(std::bad_alloc &e) {"); + _code.println(" fprintf(stderr, \"[" + p.getBasename() +"Wrapper] Memory allocation failure\\n\");"); + _code.println(" exit(1);"); + _code.println(" }"); + } + _code.println(); + + // connect the network + for (Process p : ppu) { + for (Port port : p.getPortList()) { + if (port.getName().equals(port.getBasename())) { + _code.printPrefixln(p.getName() + "->_port" + + port.getName() + "Fifo = &" + + port.getPeerResource().getName() + ";"); + } else { + _code.printPrefix(p.getName() + "->_port" + + port.getBasename() + "Fifo"); + StringTokenizer tokenizer = new StringTokenizer( + port.getName().replaceFirst( + port.getBasename(), ""), "_"); + while (tokenizer.hasMoreTokens()) { + _code.print("[" + tokenizer.nextToken() + "]"); + } + _code.println(" = &" + + port.getPeerResource().getName() + ";"); + } + } + } + _code.println(); + + /**************************************************************************/ + // This is the synchronisation code + _code.println(" for (int i = 0; i < NUM_FIFO; i++)"); + _code.println(" ((ProcessData *)ptr)->fifoTails[i] = 0;"); + _code.println(" "); + + int outQueues = 0; + + // Check all out queues on the SPU + for (Channel c : x.getChannelList()) { + + // Check all SPUs + for (Vector spu : _mapping.getSPUList()) { + if (spu.contains(c.getOrigin()) && !spu.contains(c.getTarget())) { + outQueues++; + } + } + + // Check the PPU + if (ppu.contains(c.getOrigin()) && !ppu.contains(c.getTarget())) { + outQueues++; + } + } + + // On how much queues we have to wait + _code.println(" uint32_t nrOutQueues = NUM_FIFO;"); + _code.println(" uint32_t countOutQueues = 0;"); + _code.println(" uint32_t processNr = 0;"); + _code.println(" uint32_t message;"); + _code.println(""); + + // Add all out queues of the PPE + + for (Channel c : channelList) { + if (ppu.contains(c.getOrigin()) && !ppu.contains(c.getTarget())) { + _code.println(" ((ProcessData *)ptr)->fifoTails[" + x.getChannelList().indexOf(c) + "] = (uint64_t) " + c.getName() + ".getQueuePointer();"); + _code.println(" countOutQueues++;"); + } + } + + // Check to get all messages back + _code.println(" "); + _code.println(" // Get the offset of the queues"); + _code.println(" while (countOutQueues < nrOutQueues) {"); + _code.println(" for (processNr = 0; processNr < NUM_SPES; processNr++) {"); + _code.println(" if (!spe_out_mbox_status((spe_context*)((ProcessData *)ptr)->procContentsAll[processNr])) continue;"); + _code.println(" "); + _code.println(" spe_out_mbox_read((spe_context*)((ProcessData *)ptr)->procContentsAll[processNr], &message, 1);"); + _code.println(" "); + _code.println(" uint32_t queue = ((message >> 24) & 0xFF);"); + _code.println(" uint32_t offset = ((message >> 0) & 0xFFFFFF);"); + _code.println(" "); + _code.println(" if (((ProcessData *)ptr)->queueOnSPU[queue] < 0)"); + _code.println(" ((ProcessData *)ptr)->fifoTails[queue] = offset; // Only the offset if the PPE needs to access the data"); + _code.println(" else"); + _code.println(" ((ProcessData *)ptr)->fifoTails[queue] = offset + ((ProcessData *)ptr)->ea_ls_base[processNr];"); + _code.println(" countOutQueues++;"); + _code.println(" }"); + _code.println(" }"); + _code.println(" "); + + // Send a message that each SPE knows that he can access the list + _code.println(" // Send the Okay to all SPEs for that they can start reading the tail pointers of the fifos"); + _code.println(" message = MSG_OK;"); + _code.println(" for (processNr = 0; processNr < NUM_SPES; processNr++) {"); + _code.println(" spe_in_mbox_write((spe_context*)((ProcessData *)ptr)->procContentsAll[processNr], (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING);"); + _code.println(" }"); + + /**************************************************************************/ + + // initialize processes + for (Process p : ppu) { + _code.printPrefixln(p.getName() + "->init();"); + } + _code.println(); + + // Count the effected channels + int countChannels = 0; + for (Channel c : channelList) { + if (ppu.contains(c.getOrigin()) + && ppu.contains(c.getTarget())) { + continue; + } else if (ppu.contains(c.getOrigin())) + countChannels++; + else if (ppu.contains(c.getTarget())) + countChannels++; + else + System.out.println("ERROR! Channel Mapping is wrong."); + } + + + _code.println(" FastCommunication * com;"); + _code + .println(" try { com = new FastCommunication(" + + countChannels + + ", ((ProcessData *)ptr)->procContentsAll, ((ProcessData *)ptr)->ea_ls_base, ((ProcessData *)ptr)->queueFromSPU, ((ProcessData *)ptr)->queueOnSPU, ((ProcessData *)ptr)->fifoTails); }"); + _code.println(" catch(std::bad_alloc &e) {"); + _code.println(" fprintf(stderr, \"[FastCommunication] Memory allocation failure\\n\");"); + _code.println(" exit(1);"); + _code.println(" }"); + + String channelTyp = ""; + int i = 0; + for (Channel c : channelList) { + if (ppu.contains(c.getOrigin()) + && ppu.contains(c.getTarget())) { + // Local channels have no influence on external + // communication (should be Windowed Fifos...) + channelTyp = "local"; + continue; + } else if (ppu.contains(c.getOrigin())) { + channelTyp = "out"; + } else if (ppu.contains(c.getTarget())) { + channelTyp = "in"; + } else { + System.out.println("ERROR! Channel Mapping is wrong."); + } + + if (c.getType().equals("fifo")) { + _code.println(" com->addFifo(" + (i) + ", &" + + c.getName() + ", com->" + channelTyp + ", " + + x.getChannelList().indexOf(c) + ");"); + } else if (c.getType().equals("wfifo")) { + _code.println(" com->addWFifo(" + (i) + ", &" + + c.getName() + ", com->" + channelTyp + ", " + + x.getChannelList().indexOf(c) + ");"); + } + i++; + } + + _code.printPrefixln("bool allBlocked = false;"); + _code.printPrefixln("while(!allBlocked)"); + _code.printLeftBracket(); + _code.printPrefixln("allBlocked = true;"); + for (Process p : ppu) { + _code.printPrefixln("if (!" + p.getName() + + "->isDetached()) {"); + _code.printPrefixln(" " + p.getName() + "->fire();"); + _code.printPrefixln(" allBlocked = false;"); + _code.printPrefixln("}"); + _code.printPrefixln(""); + + + + // Communication only if this process needs some communication + for (Channel c : channelList) { + // The channel may goes away + if (!ppu.contains(c.getOrigin()) || !ppu.contains(c.getTarget())) { + if (c.getOrigin().equals(p) || c.getTarget().equals(p)) { + _code.printPrefixln("com->update();"); + _code.printPrefixln(""); + break; + } + } + } + + } + _code.printRightBracket(); + _code.println(); + + _code.println(); + _code.println(" // Are there any open communication requests?"); + _code.println(" while (!com->empty())"); + _code.println(" {"); + _code.println(" com->update();"); + _code.println(" }"); + _code.println(" "); + _code.println(" // Send all SPUs the complete message"); + _code.println(" message = MSG_OK;"); + _code.println(" for (processNr = 0; processNr < NUM_SPES; processNr++) {"); + _code.println(" spe_in_mbox_write((spe_context*)((ProcessData *)ptr)->procContentsAll[processNr], (uint32_t*)&message, 1, SPE_MBOX_ANY_NONBLOCKING);"); + _code.println(" }"); + _code.println(""); + for (Process p : ppu) { + _code.printPrefixln("delete " + p.getName() + ";"); + } + _code.printPrefixln("delete com;"); + _code.println(); + _code.printPrefixln("pthread_exit(NULL);"); + _code.printRightBracket(); + } catch (Exception e) { + System.out.println("ProtothreadModuleVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void createMakefilePPU() { + try { + // Directory of the process + String ppuDir = _dir + _delimiter + "ppu"; + + // Create the filename for the new wrapper + String filename = ppuDir + _delimiter + "Makefile"; + + OutputStream file = new FileOutputStream(filename); + + PrintStream _makefilePS = new CodePrintStream(file); + + _makefilePS.println("# Makefile for PPU OS"); + _makefilePS.println(""); + _makefilePS.println("src := $(wildcard ../lib/ppu/*.cpp)"); + _makefilePS.println("obj = $(src:.cpp=.o)"); + _makefilePS.println(""); + + // The Makefile must include all o-Files of the processes needed + String dependency = ""; + + // Go through all possible processes + for (Process p : _mapping.getAllPPUBaseProcess()) { + dependency += " .." + _delimiter + "ppu_" + + p.getBasename() + _delimiter + p.getBasename() + + "Wrapper.o"; + } + + _makefilePS.println("all: ${obj}"); + _makefilePS + .println("\tppu-g++ -c -I .. -I ../lib -I ../lib/pt -I ../lib/ppu -o ppu_os.o ppu_os.cpp -ftree-vectorize -lm -mtune=cell -O3 -fmodulo-sched -funroll-loops -ffast-math"); + + _makefilePS.println(); + _makefilePS.println("clean: "); + _makefilePS.println("\trm ppu_os"); + + _makefilePS.println(); + _makefilePS.println("%.o : "); + _makefilePS + .println("\tppu-g++ -c -o $(*D)/$(*F).o $(*D)/$(*F).cpp -I .. -I ../lib -I ../lib/pt -I ../lib/ppu -ftree-vectorize -lm -mtune=cell -O3 -fmodulo-sched -funroll-loops -ffast-math"); + _makefilePS.println(); + + } catch (FileNotFoundException e) { + System.out.println("CbeProcessVisitor - Makefile: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _ppuDir = null; + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected CellMapping _mapping = null; + +} diff --git a/dol/src/dol/visitor/cell/CellProcessVisitor.java b/dol/src/dol/visitor/cell/CellProcessVisitor.java new file mode 100644 index 0000000..a0c70e4 --- /dev/null +++ b/dol/src/dol/visitor/cell/CellProcessVisitor.java @@ -0,0 +1,652 @@ +package dol.visitor.cell; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.HashMap; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.visitor.PNVisitor; +import dol.util.CodePrintStream; +import dol.util.Copier; +import dol.util.Sed; + +/** + * This class is a class for a visitor that is used to generate the main + * makefile for the application. + * + * @author lschor, 2008-10-30 + * + * Remarks: Based on a original file from wolferl for rtems + * + * Revision: 2008-10-30: Updated the file for the CBE 2008-11-08: Add + * double buffering 2009-03-17: Add protothreads + */ +public class CellProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir + * target directory + */ + public CellProcessVisitor(String dir, HashMap portMap, + CellMapping mapping) { + _dir = dir; + _portMap = portMap; + _mapping = mapping; + } + + /** + * + * @param x + * process network that needs to be processed + */ + public void visitComponent(ProcessNetwork x) { + try { + // Create the Process staff for all processes on the SPU + for (Process p : _mapping.getAllSpuBaseProcess()) { + createSPEWrapper(p); + createMakefileProcess(p); + _adaptSources("spu_" + p.getBasename(), p); + } + + // Create the Process staff for all processes on the SPU + for (Process p : _mapping.getAllPPUBaseProcess()) { + createPPEWrapper(p); + } + } catch (Exception e) { + System.out.println("CellProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates a Wrapper for an SPE process + * + * @param p + * process that needs to be processed + */ + protected void createSPEWrapper(Process p) { + try { + + // Create of each process an own folder + // Insert into this folder the source, the wrapper and a makefile + String processDir = _dir + _delimiter + "spu_" + + p.getBasename(); + File dir = new File(processDir); + dir.mkdirs(); + + /***************** class File ******************************/ + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + p.getBasename() + + "Wrapper.cpp"; + File process_file = new File(filename); + File pattern_file = new File(_dir + _delimiter + _tempDirName + + _delimiter + "spu_process_wrapper_template.cpp"); + new Copier().copyFile(pattern_file, process_file); + + String includes = ""; + for (SourceCode code : p.getSrcList()) { + includes += "#include \"" + code.getLocality() + "\"" + + System.getProperty("line.separator"); + } + + Sed sed = new Sed(); + // Replace the include of the main c-file + sed.sed(filename, "//#include \"@PROCESSNAME@.c\"", includes); + // Replace the process-name in the whole file + sed.sed(filename, "@PROCESSNAME@", p.getBasename()); + sed.sed(filename, "@PROCESSNAME_UPPER@", p.getBasename() + .substring(0, 1).toUpperCase() + + p.getBasename().substring(1)); + + /***************** header File ******************************/ + // Create the filename for the new wrapper + filename = processDir + _delimiter + p.getBasename() + + "Wrapper.h"; + process_file = new File(filename); + pattern_file = new File(_dir + _delimiter + _tempDirName + + _delimiter + "spu_process_wrapper_template.h"); + new Copier().copyFile(pattern_file, process_file); + + includes = ""; + for (SourceCode code : p.getSrcList()) { + includes += "#include \"" + code.getLocality() + "\"" + + System.getProperty("line.separator"); + } + + sed = new Sed(); + // Replace the include of the main c-file + sed.sed(filename, "//#include \"@PROCESSNAME@.c\"", includes); + // Replace the process-name in the whole file + sed.sed(filename, "@PROCESSNAME@", p.getBasename()); + + // Create the correct Port and Fifo List + String fifolist = ""; + Vector basenames = new Vector(); + for (Port port : p.getPortList()) { + if (!basenames.contains(port.getBasename())) { + basenames.add(port.getBasename()); + } else { + continue; + } + + Channel c = (Channel) port.getPeerResource(); + if (port.getName().equals(port.getBasename())) { + if (c.getType().equals("fifo")) { + fifolist += "Fifo* _port" + port.getName() + + "Fifo;"; + } else if (c.getType().equals("wfifo")) { + fifolist += "WindowedFifo* _port" + port.getName() + + "Fifo;"; + } + } else { + if (c.getType().equals("fifo")) { + fifolist += "Fifo* _port" + port.getBasename() + + "Fifo"; + } else if (c.getType().equals("wfifo")) { + fifolist += "WindowedFifo* _port" + + port.getBasename() + "Fifo"; + } + StringTokenizer tokenizer = new StringTokenizer(port + .getRange(), ";"); + while (tokenizer.hasMoreTokens()) { + fifolist += "[" + tokenizer.nextToken() + "]"; + } + fifolist += ";"; + } + } + + // Set the correct Fifo's + sed.sed(filename, "@FIFO@", fifolist); + + /***************** source File ******************************/ + + // Go through all source files + for (SourceCode sourceCode : p.getSrcList()) { + // Copy the files to the new folder + String oldFilename = _dir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + filename = processDir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (new File(filename).exists()) { + new File(filename).delete(); + } + + new File(oldFilename).renameTo(new File(filename)); + + // Copy also the c-files + if ((oldFilename.substring(oldFilename.length() - 2)) + .equals(".h")) { + String newFileNameC = filename.substring(0, filename + .length() - 2) + + ".c"; + if (new File(newFileNameC).exists()) { + new File(newFileNameC).delete(); + } + // new Copier().copyFile(new File(oldFilename.substring(0, + // oldFilename.length() - 2) + ".c"), new + // File(filename.substring(0, filename.length() - 2) + + // ".c")); + new File(oldFilename.substring(0, + oldFilename.length() - 2) + + ".c").renameTo(new File(newFileNameC)); + } + + // Update the files for the DOL project + sed.sed(filename, "", "\"dol.h\""); + + } + } catch (Exception e) { + System.out.println("CbeProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates a Wrapper for a PPE process + * + * @param p + * process that needs to be processed + */ + protected void createPPEWrapper(Process p) { + try { + // Create of each process an own folder + // Insert into this folder the source, the wrapper and a makefile + String processDir = _dir + _delimiter + "ppu_" + + p.getBasename(); + File dir = new File(processDir); + dir.mkdirs(); + + // Go through all source files + for (SourceCode sourceCode : p.getSrcList()) { + // Copy the files to the new folder + String oldFilename = _dir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + String filename = processDir + + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (new File(filename).exists()) { + new File(filename).delete(); + } + + new File(oldFilename).renameTo(new File(filename)); + + // Copy also the c-files + if ((oldFilename.substring(oldFilename.length() - 2)) + .equals(".h")) { + String newFileNameC = filename.substring(0, filename + .length() - 2) + + ".c"; + if (new File(newFileNameC).exists()) { + new File(newFileNameC).delete(); + } + new File(oldFilename.substring(0, + oldFilename.length() - 2) + + ".c").renameTo(new File(newFileNameC)); + } + } + + _createPPECppFile(dir, p); + _createPPEHeaderFile(dir, p); + _adaptSources("ppu_" + p.getBasename(), p); + createMakeFilePPUProcess("ppu_" + p.getBasename(), p); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * + */ + private void _createPPECppFile(File dir, Process p) throws IOException { + String classname = p.getBasename() + "Wrapper"; + String filename = dir + _delimiter + classname + ".cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#include \"" + classname + ".h\""); + ps.printPrefixln("#include \"dolSupport.h\""); + ps.printPrefixln("#include "); + ps.println(); + for (SourceCode sr : p.getSrcList()) { + ps.printPrefixln("#include \"" + sr.getLocality() + "\""); + } + ps.println(); + ps.printPrefixln(classname + "::" + classname + + "(char* name, int iteratorIndex[4]) :"); + ps.printPrefixln(" ProcessWrapper(name, iteratorIndex)"); + ps.printLeftBracket(); + ps.printPrefixln("_state = (LocalState)new " + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State;"); + ps.printPrefixln("_process.init = " + p.getBasename() + "_init;"); + ps.printPrefixln("_process.fire = " + p.getBasename() + "_fire;"); + ps.printPrefixln("_process.local = _state;"); + ps.printPrefixln("_process.wptr = (void*)&_wrapper_data;"); + ps.printPrefixln("_wrapper_data.wrapper = this;"); + ps.printRightBracket(); + ps.println(); + ps.printPrefixln(classname + "::~" + classname + "()"); + ps.printLeftBracket(); + ps.printPrefixln("if (_state)"); + ps.printPrefixln(" delete (" + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State*)_state;"); + // ps.printPrefixln(" delete _state;"); + ps.printRightBracket(); + } + + /** + * + */ + private void _createPPEHeaderFile(File dir, Process p) + throws IOException { + String classname = p.getBasename() + "Wrapper"; + String filename = dir + _delimiter + classname + ".h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#ifndef " + classname.toUpperCase() + "_H"); + ps.printPrefixln("#define " + classname.toUpperCase() + "_H"); + ps.println(); + ps.printPrefixln("#include \"ProcessWrapper.h\""); + ps.printPrefixln("#include \"Fifo.h\""); + ps.printPrefixln("#include \"WindowedFifo.h\""); + ps.println(); + ps.printPrefixln("class " + classname + ";"); + ps.println(); + ps.printPrefixln("typedef struct _" + p.getBasename() + "_data {"); + ps.printPrefixln(" int lc;"); + ps.printPrefixln(" " + classname + " *wrapper;"); + ps.printPrefixln("} " + p.getBasename() + "_data;"); + ps.println(); + ps + .printPrefixln("class " + classname + + " : public ProcessWrapper"); + ps.printLeftBracket(); + ps.printPrefixln("public:"); + ps.printPrefixln(" " + classname + "(char* name, " + + "int iteratorIndex[4]);"); + ps.printPrefixln(" virtual ~" + classname + "();"); + ps.println(); + + Vector basenames = new Vector(); + for (Port port : p.getPortList()) { + if (!basenames.contains(port.getBasename())) { + basenames.add(port.getBasename()); + } else { + continue; + } + + Channel c = (Channel) port.getPeerResource(); + if (port.getName().equals(port.getBasename())) { + if (c.getType().equals("fifo")) { + ps.printPrefixln(" Fifo* _port" + port.getName() + + "Fifo;"); + } else if (c.getType().equals("wfifo")) { + ps.printPrefixln(" WindowedFifo* _port" + + port.getName() + "Fifo;"); + } + } else { + if (c.getType().equals("fifo")) { + ps.printPrefix(" Fifo* _port" + port.getBasename() + + "Fifo"); + } else if (c.getType().equals("wfifo")) { + ps.printPrefix(" WindowedFifo* _port" + + port.getBasename() + "Fifo"); + } + StringTokenizer tokenizer = new StringTokenizer(port + .getRange(), ";"); + while (tokenizer.hasMoreTokens()) { + ps.print("[" + tokenizer.nextToken() + "]"); + } + ps.println(";"); + } + } + ps.println(""); + ps.printPrefixln("protected:"); + ps.printPrefixln(" struct _local_states *_state;"); + ps + .printPrefixln(" " + p.getBasename() + + "_data _wrapper_data;"); + ps.printRightBracket(); + + ps.printPrefixln(";"); + ps.println(); + ps.printPrefixln("#endif"); + } + + /** + * Create the makefile for a special process --> Each subprocess gets its + * own makefile + * + * @param p + * process for which the makefile should be created + */ + protected void createMakefileProcess(Process p) { + try { + // Directory of the process + String processDir = _dir + _delimiter + "spu_" + + p.getBasename(); + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + "Makefile"; + // File makefile = new File(filename); + + OutputStream file; + + file = new FileOutputStream(filename); + + PrintStream _makefilePS = new CodePrintStream(file); + + _makefilePS.println("# Makefile for process " + + p.getBasename()); + _makefilePS.println(""); + + String dependency = "all: " + p.getBasename() + "Wrapper.cpp"; + + for (SourceCode code : p.getSrcList()) { + dependency += " " + code.getLocality(); + } + + _makefilePS.println(dependency); + _makefilePS + .println("\tspu-g++ -c -I .. -I ../lib -o " + + p.getBasename() + + "Wrapper.o " + + p.getBasename() + + "Wrapper.cpp -ftree-vectorize -mtune=cell -O3 -fmodulo-sched -funroll-loops -ffast-math -fno-rtti -ffunction-sections -fdata-sections" ); + _makefilePS.println("clean: "); + _makefilePS.println("\trm " + p.getBasename() + "Wrapper.o"); + _makefilePS.println(); + + } catch (FileNotFoundException e) { + System.out.println("CbeProcessVisitor - Makefile: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void createMakeFilePPUProcess(String subdir, Process p) { + try { + // Directory of the process + String processDir = _dir + _delimiter + subdir; + + // Create the filename for the new wrapper + String filename = processDir + _delimiter + "Makefile"; + // File makefile = new File(filename); + + OutputStream file; + + file = new FileOutputStream(filename); + + PrintStream _makefilePS = new CodePrintStream(file); + + _makefilePS.println("# Makefile for process " + + p.getBasename()); + _makefilePS.println(""); + + String dependency = "all: " + p.getBasename() + "Wrapper.cpp"; + + for (SourceCode code : p.getSrcList()) { + dependency += " " + code.getLocality(); + } + _makefilePS.println("# General definitions:"); + _makefilePS.println("CC = ppu-g++"); + _makefilePS + .println("CCFLAGS = -ftree-vectorize -O3 -maltivec -funroll-loops -mabi=altivec -mcpu=cell"); + _makefilePS.println("COMPILE = $(CC) $(CCFLAGS) -c"); + + _makefilePS.println(dependency); + _makefilePS + .println("\t$(COMPILE) -o " + + p.getBasename() + + "Wrapper.o " + + p.getBasename() + + "Wrapper.cpp -I .. -I ../lib -I ../lib/ppu -I ../lib/pt;"); + _makefilePS.println("clean: "); + _makefilePS.println("\trm " + p.getBasename() + "Wrapper.o"); + _makefilePS.println(); + + } catch (FileNotFoundException e) { + System.out + .println("CbeProcessVisitor - Makefile PPU: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Make modifications to source files of a process. Port names need to be + * strings for the SystemC code generation. Therefore, in the header files + * integer port names are put into quotation marks. + * + * @param p + * process whose sources should be adapted + * @throws IOException + * @author haidw + */ + protected void _adaptSources(String subdir, Process p) + throws IOException { + Sed sed = new Sed(); + // modify header file + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + + _delimiter + + subdir + + _delimiter + + sr.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + sed.sed(processHeaderFile, "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?", "$1 " + + "static_cast<" + p.getBasename() + + "Wrapper *>((static_cast<" + p.getBasename() + + "_data *>(p->wptr))->wrapper)->_port" + + port.getBasename() + "Fifo"); + } + } + + // modify source file + for (SourceCode sr : p.getSrcList()) { + String processSourceFile = _dir + _delimiter + subdir + + _delimiter + sr.getLocality(); + + String line; + StringBuffer buffer = new StringBuffer(); + FileInputStream fileInputStream = new FileInputStream( + processSourceFile); + BufferedReader reader = new BufferedReader( + new InputStreamReader(fileInputStream)); + while ((line = reader.readLine()) != null) { + buffer.append(line + "\n"); + } + reader.close(); + + String file = buffer.toString(); + // insert PT_BEGIN() at beginning of fire() function + file = file.replaceAll("(int[ ]*" + p.getBasename() + + "_fire[ ]*\\([ ]*DOLProcess[ ]*\\*p[ ]*\\)" + + "[\\s\\S&&[^\\{]]*)\\{", "$1" + + System.getProperty("line.separator") + "{" + + System.getProperty("line.separator") + + " PT_BEGIN((pt*)(p->wptr));"); + + // replace last return statement in fire function by PT_END() + // find beginning of fire function + Matcher matcher = Pattern.compile( + "(int[ ]*" + p.getBasename() + + "_fire[ ]*\\([ ]*DOLProcess[ ]*\\*p[ ]*\\)" + + "[\\s\\S&&[^\\{]]*)\\{").matcher(file); + matcher.find(); + int i = 0; + try { + i = matcher.start(); + } catch (Exception e) { + System.out.println("Error: could not find " + + p.getBasename() + "_fire() function in " + + processSourceFile + "."); + e.printStackTrace(); + } + int openBraces = 0; // counter for open curly braces + // position of last return statement + int lastReturnStartPosition = 0; + int lastReturnEndPosition = 0; + while (i < file.length()) { + // ignore single-line comments + if (i < (file.length() - 1) + && file.substring(i, i + 2).equals("//")) { + while (!file.substring(i, i + 1).equals("\n")) { + i++; + } + } + // ignore multi-line comments + else if (i < (file.length() - 1) + && file.substring(i, i + 2).equals("/*")) { + while (!file.substring(i, i + 2).equals("*/")) { + i++; + } + } + // ignore strings + else if (file.substring(i, i + 1).equals("\"")) { + matcher = Pattern.compile("[\\s\\S&&[^\\\\]]\\\"") + .matcher(file); + matcher.find(i + 1); + i = matcher.start() + 1; + } else if (i < (file.length() - 5) + && file.substring(i, i + 6).equals("return")) { + lastReturnStartPosition = i; + while (!file.substring(i, i + 1).equals(";")) { + i++; + } + lastReturnEndPosition = i; + } else if (file.substring(i, i + 1).equals("{")) { + openBraces++; + } else if (file.substring(i, i + 1).equals("}")) { + openBraces--; + if (openBraces == 0) { + break; + } + } + i++; + } + + file = file.substring(0, lastReturnStartPosition) + + "/* " + + file.substring(lastReturnStartPosition, + lastReturnEndPosition + 1) + + " (commented out by DOL) */" + + System.getProperty("line.separator") + + " PT_END((pt*)(p->wptr));" + + System.getProperty("line.separator") + + file.substring(lastReturnEndPosition + 2, file + .length()); + + BufferedWriter out = new BufferedWriter(new FileWriter( + processSourceFile)); + out.write(file); + out.close(); + } + } + + protected CellMapping _mapping; + protected String _dir = null; + protected HashMap _portMap; + + protected static String _libDirName = "lib"; + protected static String _tempDirName = "template"; + +} diff --git a/dol/src/dol/visitor/cell/CellSPEVisitor.java b/dol/src/dol/visitor/cell/CellSPEVisitor.java new file mode 100644 index 0000000..a8061ec --- /dev/null +++ b/dol/src/dol/visitor/cell/CellSPEVisitor.java @@ -0,0 +1,416 @@ +package dol.visitor.cell; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; +import dol.util.CodePrintStream; + +/** + * This class is a class for a visitor that is used to generate the the OS for + * the SPEs. + * + * @author lschor, 2009-03-24 * Revision: 2009-03-24: Created + */ +public class CellSPEVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir + * path of this file + */ + public CellSPEVisitor(String dir, CellMapping mapping) { + _dir = dir; + _mapping = mapping; + } + + /** + * Visit process network. + * + * @param x + * process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + // Create a directory where all SPU's are stored: + _spuDir = _dir + _delimiter + "spu"; + File dir = new File(_spuDir); + dir.mkdirs(); + + // Copy the header file to this directory + // Some library files must be copied to the main directory + (new File(_dir + _delimiter + "lib" + _delimiter + "spu_os.h")) + .renameTo(new File(dir.getPath() + _delimiter + + "spu_os.h")); + + ArrayList> spuList = _mapping.getSPUList(); + + int i = 0; + for (Vector spu : spuList) { + createOSLayer(x, spu, i++); + } + + createMakefileSPU(); + + } catch (Exception e) { + System.out.println("CellSPEVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create the makefile for a SPU (so for the layer). Each SPU gets its own + * Makefile + * + * @param spu + * SPU for which the Makefile should be used + */ + protected void createMakefileSPU() { + try { + // Directory of the process + String spuDir = _dir + _delimiter + "spu"; + + // Create the filename for the new wrapper + String filename = spuDir + _delimiter + "Makefile"; + + OutputStream file = new FileOutputStream(filename); + + PrintStream _makefilePS = new CodePrintStream(file); + + _makefilePS.println("# Makefile for SPU OS"); + _makefilePS.println(""); + _makefilePS.println("src := $(wildcard ../lib/spu/*.cpp)"); + _makefilePS.println("obj = $(src:.cpp=.o)"); + _makefilePS.println(""); + + // The Makefile must include all o-Files of the processes needed + String dependency = ""; + + // Go through all possible processes + for (Process p : _mapping.getAllSpuBaseProcess()) { + dependency += " .." + _delimiter + "spu_" + + p.getBasename() + _delimiter + p.getBasename() + + "Wrapper.o"; + } + + _makefilePS.println("all: ${obj}" + dependency); + for (int i = 0; i < _mapping.getNrSPE(); i++) { + _makefilePS.println("\tspu-g++ -Wall -I .. -I ../lib -o " + + "spu_os_" + i + " spu_os_" + i + ".cpp " + + "../lib/spu/Fifo.o ../lib/spu/WindowedFifo.o " + + "../lib/spu/dolSupport.o " + + "../lib/spu/proc_wrapper.o ../lib/spu/common.o " + + "../lib/spu/FastCommunication.o" + dependency + + " -ftree-vectorize -lm -mtune=cell -O3" + + " -fmodulo-sched -funroll-loops " + + " -ffast-math -fno-rtti -ffunction-sections" + + " -fdata-sections -Wl,--gc-sections"); + } + _makefilePS.println(); + _makefilePS.println("clean: "); + for (int i = 0; i < _mapping.getNrSPE(); i++) { + _makefilePS.println("\trm spu_os_" + i); + } + _makefilePS.println(); + _makefilePS.println("%.o : "); + _makefilePS.println("\tspu-g++ -c -o $(*D)/$(*F).o " + + "$(*D)/$(*F).cpp -I .. -I" + + " ../lib -ftree-vectorize -mtune=cell -O3" + + " -fmodulo-sched -funroll-loops -ffast-math" + + " -fno-rtti -ffunction-sections -fdata-sections"); + _makefilePS.println(); + } catch (FileNotFoundException e) { + System.out.println("CbeProcessVisitor - Makefile: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates the OS layer for one SPU + * + * @param spu + * List of all processes a SPU gets + * @param index + * index of the SPU in the SPU list + */ + protected void createOSLayer(ProcessNetwork x, Vector spu, + int index) { + try { + // Create the filename for the new layer + String filename = _spuDir + _delimiter + "spu_os_" + index + + ".cpp"; + + // PrintStream for writing to the file + OutputStream file = new FileOutputStream(filename); + CodePrintStream _code = new CodePrintStream(file); + + _code.println("/****************************************************************"); + _code.println(" * SPU OS file"); + _code.println(" * Description: OS for one SPU"); + _code.println(" */"); + _code.println(); + _code.println("#include \"spu_os.h\""); + + // Must include all possible wrappers + Vector pList = new Vector(); + // Go through all possible processes + for (Process p : spu) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + _code.println("#include \".." + _delimiter + "spu_" + + p.getBasename() + _delimiter + + p.getBasename() + "Wrapper.h\""); + } + } + + _code.println(""); + _code.println("// Main application"); + _code.println("int main(int speid , uint64_t argp)"); + _code.println("{"); + _code.println(" // reserve DMA tag ID"); + _code.println(" uint32_t tag_id;"); + _code.println(" if((tag_id=mfc_tag_reserve())==MFC_TAG_INVALID){"); + _code.println(" printf(\"SPE: ERROR - can't reserve a tag ID\"); return 1;"); + _code.println(" }"); + _code.println(" "); + _code.println(" // Get the context information for the whole system"); + _code.println(" mfc_get((void*) &ctx_spu, argp, sizeof(ctx_spu), tag_id, 0, 0);"); + _code.println(" mfc_write_tag_mask(1< channelList = new Vector(); + int indx = 0; + // instantiate channels + for (Channel c : x.getChannelList()) { + if (spu.contains(c.getOrigin()) + || spu.contains(c.getTarget())) { + channelList.add(c); + if (c.getType().equals("fifo")) { + _code.printPrefixln(" Fifo " + c.getName() + "(" + + "FIFO_SIZE[" + indx + "]);"); + } else if (c.getType().equals("wfifo")) { + _code.printPrefixln(" WindowedFifo " + + c.getName() + "(" + "FIFO_SIZE[" + indx + + "]);"); + } + } + indx++; + } + + _code.println(); + + // Go through all processes + for (Process p : spu) { + // Create the process wrapper + _code.println(" " + p.getBasename() + "Wrapper *" + p.getName() +";"); + _code.println(" try { " + + p.getName() + " = new " + p.getBasename() + + "Wrapper (context_addr[" + spu.indexOf(p) + + "]); }"); + _code.println(" catch(std::bad_alloc &e) {"); + _code.println(" fprintf(stderr, \"[" + p.getBasename() +"Wrapper] Memory allocation failure\\n\");"); + _code.println(" exit(1);"); + _code.println(" }"); + + // Create the port-mapping + for (Port port : p.getPortList()) { + if (port.getName().equals(port.getBasename())) { + _code.printPrefixln(" " + p.getName() + "->_port" + + port.getName() + "Fifo = &" + + port.getPeerResource().getName() + ";"); + } else { + _code.printPrefix(" " + p.getName() + "->_port" + + port.getBasename() + "Fifo"); + StringTokenizer tokenizer = new StringTokenizer( + port.getName().replaceFirst( + port.getBasename(), ""), "_"); + while (tokenizer.hasMoreTokens()) { + _code.print("[" + tokenizer.nextToken() + "]"); + } + _code.println(" = &" + + port.getPeerResource().getName() + ";"); + } + } + _code.println(); + } + + // Count the effected channels + int countChannels = 0; + for (Channel c : channelList) { + if (spu.contains(c.getOrigin()) + && spu.contains(c.getTarget())) { + continue; + } else if (spu.contains(c.getOrigin())) + countChannels++; + else if (spu.contains(c.getTarget())) + countChannels++; + else + System.out.println("ERROR! Channel Mapping is wrong."); + } + + _code.println(" // inited all queues, now one has to know:"); + _code.println(" // sends out the start addresses to the SPU (of all out channels)"); + + // Check all out queues on the SPU + _code.println(" uint32_t queuemessage;"); + for (Channel c : channelList) { + if (spu.contains(c.getOrigin()) && !spu.contains(c.getTarget())) { + _code.println(" queuemessage = CREATEQUEUEMESSAGE(" + x.getChannelList().indexOf(c) + ", (uint32_t)" + c.getName() + ".getQueuePointer());"); + _code.println(" spu_write_out_mbox(queuemessage);"); + } + } + + _code.println(" "); + _code.println(" // Wait until we get the okey to read the tail pointers"); + _code.println(" uint32_t messageIn = spu_read_in_mbox();"); + _code.println(""); + _code.println(" mfc_get((void*)tailAddresses, ctx_spu.fifoTails, sizeof(uint64_t) * roundDMA(NUM_FIFO), tag_id,0,0);"); + _code.println(" mfc_write_tag_mask(1<addFifo(" + (i) + ", &" + + c.getName() + ", com->" + channelTyp + ", " + + x.getChannelList().indexOf(c) + ");"); + } + else if (c.getType().equals("wfifo")) { + _code.println(" com->addWFifo(" + (i) + ", &" + + c.getName() + ", com->" + channelTyp + ", " + + x.getChannelList().indexOf(c) + ");"); + } + i++; + } + + // The scheduler (yes, this could be optimized...) + _code.println(""); + _code.println(" printf(\"SPU " + index + ": start to execute\\n\"); "); + _code.println(""); + _code.println(" bool allBlocked = false;"); + _code.println(" while (!allBlocked)"); + _code.println(" {"); + _code.println(" allBlocked = true;"); + for (Process p : spu) { + _code.println(" if (!" + p.getName() + + "->isDetached())"); + _code.println(" {"); + _code.println(" " + p.getName() + "->fire();"); + _code.println(" allBlocked = false;"); + _code.println(" }"); + + // communication only if this process needs some communication + for (Channel c : channelList) { + // the channel may go away + if (!spu.contains(c.getOrigin()) || !spu.contains(c.getTarget())) { + if (c.getOrigin().equals(p) || c.getTarget().equals(p)) { + _code.println(" com->update();"); + break; + } + } + } + _code.println(); + } + _code.println(" }"); + _code.println(); + + // End --> wait until all communications are finished + _code + .println(" // Are there any open communication requests?"); + _code.println(" while (!com->empty())"); + _code.println(" {"); + _code.println(" com->update();"); + _code.println(" }"); + + for (Process p : spu) { + _code.println(" delete " + p.getName() + ";"); + } + + _code.println(" delete com;"); + _code.println(" "); + _code.println(" // release tag ID before exiting"); + _code.println(" mfc_tag_release(tag_id);"); + _code.println(" "); + _code.println(" uint32_t message = CREATEFASTMESSAGE(SPE_COMPLETE, 0, 0);"); + _code.println(" spu_write_out_mbox(message);"); + _code.println(" "); + _code.println(" messageIn = spu_read_in_mbox();"); + _code.println("}"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + protected String _spuDir = null; + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected CellMapping _mapping = null; +} diff --git a/dol/src/dol/visitor/cell/CellVisitor.java b/dol/src/dol/visitor/cell/CellVisitor.java new file mode 100644 index 0000000..43c609d --- /dev/null +++ b/dol/src/dol/visitor/cell/CellVisitor.java @@ -0,0 +1,179 @@ +package dol.visitor.cell; + +import java.io.File; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.main.UserInterface; +import dol.visitor.PNVisitor; +import dol.util.Copier; + +/** + * This class is a class for a visitor that is used to generate + * a CELL package. + */ +public class CellVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param packageName name of the Cell directory + */ + public CellVisitor(String packageName) { + _packageName = packageName; + _ui = UserInterface.getInstance(); + } + + /** + * Visit process network. + * + * @param pn process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork pn) { + try { + File dir = new File(_packageName); + dir.mkdirs(); + + // Create the library + File lib = new File(_packageName + _delimiter + "lib"); + lib.mkdirs(); + + //copy library files + File source = new File(_ui.getMySystemCLib(). + replaceAll("systemC", "cell").replace("%20", " ")); + new Copier().copy(source, lib); + + // Create the template + File template = new File(_packageName + _delimiter + "template"); + template.mkdirs(); + + //copy the templates + source = new File(_ui.getMySystemCLib().replaceAll("systemC", "cell").replace("lib", "template").replace("%20", " ")); + new Copier().copy(source, template); + + // Some library files must be copied to the main directory + (new File(lib.getPath() + _delimiter + "ppu_main.h")).renameTo(new File (dir.getPath() + _delimiter + "ppu_main.h")); + + //copy process source code + source = new File(_srcDirName.replace("%20", " ")); + new Copier().copy(source, dir); + + createPortMap(pn); + + // Create the mapping for this process network on the cell + CellMapping mapping = new CellMapping(pn, "predefined", true, MAX_SPUS); + + pn.accept(new CellMakefileVisitor(_packageName, mapping)); + pn.accept(new CellProcessVisitor(_packageName, _portMap, mapping)); + pn.accept(new CellModuleVisitor(_packageName, _portMap, mapping)); + pn.accept(new CellConstantVisitor(_packageName, mapping)); + pn.accept(new CellSPEVisitor(_packageName, mapping)); + pn.accept(new CellPPEVisitor(_packageName, mapping)); + } + catch (Exception e) { + System.out.println("CellVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create a hashmap which maps each port of the given process network + * to an integer. For each process, ports are numbered with integers + * starting from 0. + * + * @param pn process network for which the map should be generated + */ + protected void createPortMap(ProcessNetwork pn) { + _portMap = new HashMap(); + + for (Process process : pn.getProcessList()) { + int portCount = 0; + Vector portList = process.getPortList(); + Vector portNameList = new Vector(); + portNameList.clear(); + HashMap portMap = + new HashMap(); + portMap.clear(); + + for (int i = 0; i < portList.size(); i++) { + //treat single ports differently than iterated ports + String portName = portList.elementAt(i).getName(); + String baseName = portList.elementAt(i).getBasename(); + + if (portName.equals(baseName)) { + portNameList.add(portName); + portMap.put(portName, portCount++); + } else { + String range_indices = portList.elementAt(i).getRange(); + Vector range_indices_values = getIndex(range_indices, ";"); + + String port_indices = portName; + port_indices.replaceAll(baseName, ""); + Vector port_indices_values = getIndex(port_indices, "_"); + + if (!portNameList.contains(baseName)) { + portNameList.add(baseName); + portMap.put(baseName, portCount); + + int size = 1; + for (int j = 0; j < range_indices_values.size(); j++) { + size *= range_indices_values.elementAt(j); + } + portCount += size; + } + + int portId = portMap.get(baseName); + for (int j = 0; j < port_indices_values.size(); j++) { + int weight = 1; + for (int k = j + 1; k < range_indices_values.size(); k++) { + weight *= range_indices_values.elementAt(k); + } + portId += port_indices_values.elementAt(j) * weight; + } + portMap.put(portName, portId); + } + } + + for (int i = 0; i < portList.size(); i++) { + _portMap.put(portList.elementAt(i), + portMap.get(portList.elementAt(i).getName())); + } + } + } + + /** + * Gets vector of indices of a string, where the index must be + * separated by the specified separator. + * examples: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param range string to parse + * @param separator delimiter of indices + * @return vector of indices + */ + protected Vector getIndex(String range, String separator) { + Vector indices = new Vector(); + String[] subranges = range.split(separator); + for (int i = 0; i < subranges.length; i++) { + try { + int value = Integer.valueOf(subranges[i]); + indices.add(value); + } catch (Exception e) { + continue; + } + } + return indices; + } + + public final static int MAX_SPUS = 6; + protected HashMap _portMap; + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; +} diff --git a/dol/src/dol/visitor/cell/lib/common.h b/dol/src/dol/visitor/cell/lib/common.h new file mode 100644 index 0000000..b890fb9 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/common.h @@ -0,0 +1,91 @@ +/**************************************************************** + * COMMON.H + * Creator: lschor, 2008-10-30 + * Description: Specifies some structs and Constants for the CBE-DOL-Implementation + * + * Revision: + * - 2008-10-30: Created + */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include +#include + +#include "dol.h" +#include "constant.h" + +#define SPE_READ_DEMAND 0 +#define SPE_READ_COMPLETE 1 +#define SPE_COMPLETE 2 + +// Context for one process --> 384 bit +typedef struct{ + uint64_t port_id; + uint64_t port_queue_id; + + uint64_t processName; // Address for the name of the process + uint64_t processNameLen; // Len of the process Name + + uint32_t number_of_ports; + uint32_t is_detached; + uint32_t padd[2]; // dummy - for alignment --> It always has to be a multiple of 128 bit! +} process_context; + +// Context for one SPU --> This is send to him! +typedef struct{ + uint64_t procContents; + uint64_t procContentsLen; + + uint64_t procContentsAll; + uint64_t queueFromSPU; + uint64_t queueOnSPU; + + uint64_t fifoTails; + + uint64_t ea_base; // Base address of the context + uint32_t padd[2]; // dummy - for alignment --> It always has to be a multiple of 128 bit! +} spu_context; + + +// Create a message to send the queue offset +// 8 bit: queue number +// 24 bit: offset of the LS +#define CREATEQUEUEMESSAGE(_queue, _offset) \ + ((_queue << 24) | (_offset)) + +#define QUEUEMSGQUEUE(_message) \ + ((_message >> 24) & 0xFF) + +#define QUEUEMSGOFFSET(_message) \ + ((_message >> 0) & 0xFFFFFF) + + +// Create the message we like to send, format: +// 4 bit: code (total 16 possibilities) +// 9 bit: queue (total 512 possibilities) +// 19 bit: len +#define CREATEFASTMESSAGE(_code, _queue, _len) \ + ((_code << 28) | (_queue << 19) | (_len)) + +// Get the code from a message +#define GETFASTCODE(_message) \ + ((_message >> 28) & 0xF) + +// Get the queue from a message +#define GETFASTQUEUE(_message) \ + ((_message >> 19) & 0x1FF) + +// Get the len from a message +#define GETFASTLEN(_message) \ + ((_message >> 0) & 0x7FFFF) + +#define MSG_OK 19 + +// Round a number ot the right DMA number --> Is used for DMA transfers +uint32_t roundDMA(uint32_t number); + +#endif // _COMMON_H_ + diff --git a/dol/src/dol/visitor/cell/lib/dol.h b/dol/src/dol/visitor/cell/lib/dol.h new file mode 100644 index 0000000..297f8c5 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/dol.h @@ -0,0 +1,34 @@ +#ifndef __DOL_H__ +#define __DOL_H__ + +// structure for local memory of process +typedef struct _local_states *LocalState; + +// structure for process +struct _process; + +// standard functions +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; +} DOLProcess; + +void DOL_read(void *port, void *buf, int len, DOLProcess *process); +void DOL_write(void *port, void *buf, int len, DOLProcess *process); + +unsigned DOL_reserve(void* port, void** destination, unsigned len, DOLProcess* process); +void DOL_release(void* port, DOLProcess* process); +unsigned DOL_capture(void* port, void** destination, unsigned len, DOLProcess* process); +void DOL_consume(void* port, DOLProcess* process); + +void DOL_detach(DOLProcess *process); +int getIndex(const char* string, char* tokens, int indexNumber); +int *createPort(int *port, int base, int number_of_indices, int index_range_pairs, ...); + +#endif diff --git a/dol/src/dol/visitor/cell/lib/estimation.h b/dol/src/dol/visitor/cell/lib/estimation.h new file mode 100644 index 0000000..eb1570a --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/estimation.h @@ -0,0 +1,50 @@ +/**************************************************************** + * Estimation Defintions + * Creator: lschor, 2008-11-15 + * Description: File with includes for time measurements + * Principle: if 1 then this point will be measured, if 0 then this point will not be measured + * + * Revision: + * - 2008-11-15: Created + */ + +// +#ifndef __ESTIMATION_H__ +#define __ESTIMATION_H__ + +// What to measure? +//#define MEASURE 0 // Measure activeted + +#ifdef MEASURE + //#define MEASURE_DOL_READ 0 // Measure DOL READ + //#define MEASURE_DOL_READ_FINISH 0 // Measure FINISH DOL Read + //#define MEASURE_DOL_READ_START_DMA 0 // Measure Time from start until the DMA process has started + //#define MEASURE_DOL_READ_HANDSHAKE 0 // Measure Time of the whole handshake + //#define MEASURE_DOL_READ_DMA 0 // Measure Time of DMA Setup + //#define MEASURE_DOL_READ_LOCBUF 0 // Measure Time for LocBuf read + //#define MEASURE_DOL_READ_DOUBLEBUF 0 // Measure Time for writing into the buffer + + //#define MEASURE_DOL_WRITE 0 // Measure DOL WRITE + //#define MEASURE_DOL_WRITE_FINISH 0 // Measure FINISH DOL Write + //#define MEASURE_DOL_WRITE_START_DMA 0 // Measure Time from start until the DMA process has started + //#define MEASURE_DOL_WRITE_HANDSHAKE 0 // Measure Time of the whole handshake + //#define MEASURE_DOL_WRITE_DMA 0 // Measure Time of DMA Setup + + //#define MEASURE_DOL_FIRE 0 // Measure DOL FIRE + //#define MEASURE_DOL_INIT 0 // Measure DOL INIT + //#define MEASURE_SPE 0 // Measure whole SPE process + + //#define MEASURE_APPLICATION 0 // Measure whole execution time + //#define MEASURE_SET_UP_SPE_THREAD 0 // Measure time to set up the SPE-threads + //#define MEASURE_SPE_WRITE_DEMAND 0 // Measure time of write demand + //#define MEASURE_SPE_READ_DEMAND 0 // Measure time of read demand + //#define MEASURE_SPE_WRITE_SUC 0 // Measure time of write successful + //#define MEASURE_SPE_READ_SUC 0 // Measure time of read successful + +#endif + +// Some constants +#define MEASURE_START 0xFFFFFFFF // Start decrementer at this value +#define MEASURE_CPU 79800000.0 // Timebase PS3 (in Hz) + +#endif diff --git a/dol/src/dol/visitor/cell/lib/free_align.h b/dol/src/dol/visitor/cell/lib/free_align.h new file mode 100644 index 0000000..5924871 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/free_align.h @@ -0,0 +1,65 @@ +/* -------------------------------------------------------------- */ +/* (C)Copyright 2001,2006, */ +/* International Business Machines Corporation, */ +/* Sony Computer Entertainment, Incorporated, */ +/* Toshiba Corporation, */ +/* */ +/* All Rights Reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the */ +/* following conditions are met: */ +/* */ +/* - Redistributions of source code must retain the above copyright*/ +/* notice, this list of conditions and the following disclaimer. */ +/* */ +/* - Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* - Neither the name of IBM Corporation nor the names of its */ +/* contributors may be used to endorse or promote products */ +/* derived from this software without specific prior written */ +/* permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND */ +/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR */ +/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ +/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT */ +/* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; */ +/* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) */ +/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN */ +/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR */ +/* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */ +/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* -------------------------------------------------------------- */ +/* PROLOG END TAG zYx */ +#ifndef _FREE_ALIGN_H_ +#define _FREE_ALIGN_H_ 1 + +#include + +/* Function + * + * void free_align(void *ptr) + * + * Description + * The free_align routine frees a memory buffer allocate by the + * malloc_align routine. See malloc_align for complete details. + */ + +static __inline void _free_align(void *ptr) +{ + void * real; + + if (ptr) { + real = *((void **)(ptr)-1); + free(real); + } +} + +#endif /* _FREE_ALIGN_H_ */ diff --git a/dol/src/dol/visitor/cell/lib/malloc_align.h b/dol/src/dol/visitor/cell/lib/malloc_align.h new file mode 100644 index 0000000..0a19068 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/malloc_align.h @@ -0,0 +1,105 @@ +/* -------------------------------------------------------------- */ +/* (C)Copyright 2001,2007, */ +/* International Business Machines Corporation, */ +/* Sony Computer Entertainment, Incorporated, */ +/* Toshiba Corporation, */ +/* */ +/* All Rights Reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the */ +/* following conditions are met: */ +/* */ +/* - Redistributions of source code must retain the above copyright*/ +/* notice, this list of conditions and the following disclaimer. */ +/* */ +/* - Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* - Neither the name of IBM Corporation nor the names of its */ +/* contributors may be used to endorse or promote products */ +/* derived from this software without specific prior written */ +/* permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND */ +/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR */ +/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ +/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT */ +/* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; */ +/* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) */ +/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN */ +/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR */ +/* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */ +/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* -------------------------------------------------------------- */ +/* PROLOG END TAG zYx */ + +#ifndef _MALLOC_ALIGN_H_ +#define _MALLOC_ALIGN_H_ 1 + +#include + +/* Function + * + * void * malloc_align(size_t size, unsigned int log2_align) + * + * Description + * The malloc_align routine allocates a memory buffer of + * bytes aligned to the power of 2 alignment specified by . + * For example, malloc_align(4096, 7) will allocate a memory heap + * buffer of 4096 bytes aligned on a 128 byte boundary. + * + * The aligned malloc routine allocates an enlarged buffer + * from the standard memory heap. Space for the real allocated memory + * pointer is reserved on the front of the memory buffer. + * + * ----------------- <--- start of allocated memory + * | pad 0 to | + * |(1< (2) + { + // Find out for which fifo the request is + fifoCollection *fifocol = NULL; + int i; + for (i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].queue == queue) { + fifocol = &_fifos[i]; + break; + } + } + + // The queue was not found + if (fifocol == NULL) { + printf("PPU COM> ERROR, this queue does not exists!\n"); + return false; + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Windowed FIFO + if (_fifos[i].iswfifo) { + WindowedFifo* wfifo = NULL; + wfifo = _fifos[i].wfifo; + + uint32_t inTail = wfifo->_inTail; + + // This is the len we like to read + len = len > (wfifo->unused()) ? wfifo->unused() : len; + + // We can read something + if (len > 0) { + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + uint64_t baseAddress = _fifoTails[queue] + inTail; + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset + uint32_t offset = _fifoTails[queue] + inTail + - baseAddress; + + // Store all data in the request buffer + request->data = (char *) _malloc_align(roundDMA(len + + offset), ALIGNMENT_FACTOR); + request->len = len; + request->wfifo = wfifo; + request->iswfifo = true; + request->status = read_started; + request->queue = queue; + request->offset = offset; + + int ret; + do { + ret = spe_mfcio_put( + (spe_context*) _context_all[processNr], + baseAddress, (void *) &(request->data[0]), + roundDMA(len + offset), request->tag_id, + 0, 0); + } while (ret != 0); + } else { +#ifndef STORE_REQUESTS // Send len = 0 back to the sender, this one should try it in a later phase + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); +#else // I store the request and may try in a later time to start the transfer + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) + { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE(SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + request->len = len; + request->wfifo = wfifo; + request->iswfifo = true; + request->status = read_pending; + request->queue = queue; +#endif + } + + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Classic FIFO + else { + Fifo* fifo = NULL; + fifo = _fifos[i].fifo; + + uint32_t inTail = fifo->_inTail; + + // This is the len we like to read + len = len > (fifo->unused()) ? fifo->unused() : len; + + // We can read something + if (len > 0) { + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + uint64_t baseAddress = _fifoTails[queue] + inTail; + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset + uint32_t offset = _fifoTails[queue] + inTail + - baseAddress; + + // Store all data in the request buffer + request->data = (char *) _malloc_align(roundDMA(len + + offset), ALIGNMENT_FACTOR); + request->len = len; + request->fifo = fifo; + request->iswfifo = false; + request->status = read_started; + request->queue = queue; + request->offset = offset; + + int ret; + do { + ret = spe_mfcio_put( + (spe_context*) _context_all[processNr], + baseAddress, (void *) &(request->data[0]), + roundDMA(len + offset), request->tag_id, + 0, 0); + } while (ret != 0); + } + + // Len == 0 + else { +#ifndef STORE_REQUESTS // Send len = 0 back to the sender, this one should try it in a later phase + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); +#else // I store the request and may try in a later time to start the transfer + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE(SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + request->len = len; + request->fifo = fifo; + request->iswfifo = false; + request->status = read_pending; + request->queue = queue; +#endif + } + } + } + + /***************************************************************************************************************/ + else if (code == SPE_READ_COMPLETE) // Can finish a write request (4) --> (5) + { + // Get the stored request + comRequest* request = getRequest(read_request_sent, queue); + + if (request == NULL) { + printf("PPU Communicate> Couldn't find the request\n"); + return 0; + } + + // Inform my FIFO that the request is completed + if (request->iswfifo) + request->wfifo->dmaRead(len); + else + request->fifo->dmaRead(len); + + // Request free + deleteRequest(request); + } + + /***************************************************************************************************************/ + else if (code == SPE_COMPLETE) // One SPE has finished + { + _nrSpeComplete++; + } + } + + /***************************************************************************************************************/ + // Check if some active write processes have finished (4) + uint8_t req = _currentRequest; + + for (int i = 0; i < _nrOfRequest; i++) { + if (_request[req].valid) { + if (_request[req].status == read_started) { + if (!(testMessage(queueFromSPE[_request[req].queue]) > 0)) + continue; + + uint32_t tag_id = _request[req].tag_id; + uint32_t ret; + uint32_t + test = + spe_mfcio_tag_status_read( + (spe_context*) _context_all[queueFromSPE[_request[req].queue]], + 0, SPE_TAG_IMMEDIATE, &ret); + + if (((ret & (1 << tag_id)) == 0)) { + // Have to write the data into the fifo + if (_request[req].iswfifo) { // WFIFO + + _request[req].wfifo->dmaWrite( + (char *) _request[req].data + + _request[req].offset, + _request[req].len); + _free_align(_request[req].data); + + // Increase the inTail value (used to know where the last request was started) + + _request[req].wfifo->_inTail + = (_request[req].wfifo->_inTail + + _request[req].len) % (FIFO_SIZE[_request[req].queue]); + } else { // FIFO + _request[req].fifo->write( + (char *) _request[req].data + + _request[req].offset, + _request[req].len); + _free_align(_request[req].data); + + _request[req].fifo->_inTail + = (_request[req].fifo->_inTail + + _request[req].len) % (FIFO_SIZE[_request[req].queue]); + + } + + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, _request[req].queue, + _request[req].len); + sendMessage(message, queueFromSPE[_request[req].queue]); + + deleteRequest(&_request[req]); + _currentRequest = (req + 1) % _nrOfRequest; + + break; + } + } else if (_request[req].status == read_pending) { // Has an open request for sending + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Windowed FIFO + if (_request[req].iswfifo) { + + if (_request[req].wfifo->unused() > 0) { + comRequest *request = &_request[req]; + + // This is the len we like to read + uint32_t len = request->len > (request->wfifo->unused()) ? request->wfifo->unused() + : request->len; + + uint32_t inTail = request->wfifo->_inTail; + uint64_t baseAddress = _fifoTails[request->queue] + + inTail; + + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset --> How much we had to align + uint32_t offset = _fifoTails[request->queue] + + inTail - baseAddress; + + request->data = (char *) _malloc_align(roundDMA( + len + offset), ALIGNMENT_FACTOR); + request->len = len; + request->status = read_started; + request->offset = offset; + + // Set up the request in the MFC + int ret; + do { + ret = spe_mfcio_put( + (spe_context*) _context_all[queueFromSPE[_request[req].queue]], + baseAddress, + (void *) &(request->data[0]), + roundDMA(len + offset), + request->tag_id, 0, 0); + } while (ret != 0); + } + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Classic FIFO + else { + + if (_request[req].fifo->unused() > 0) { + comRequest *request = &_request[req]; + + // This is the len we like to read + uint32_t len = request->len > (request->fifo->unused()) ? request->fifo->unused() : request->len; + + uint32_t inTail = request->fifo->_inTail; + uint64_t baseAddress = _fifoTails[request->queue] + + inTail; + + while (baseAddress % 16 != 0) + baseAddress--; + + // Offset + uint32_t offset = _fifoTails[request->queue] + + inTail - baseAddress; + + // Store all data in the request buffer + request->data = (char *) _malloc_align(roundDMA( + len + offset), ALIGNMENT_FACTOR); + request->len = len; + request->status = read_started; + request->offset = offset; + + // Start the DMA transfer + int ret; + do { + ret + = spe_mfcio_put( + (spe_context*) _context_all[queueFromSPE[_request[req].queue]], + baseAddress, + (void *) &(request->data[0]), + roundDMA(len + offset), + request->tag_id, 0, 0); + } while (ret != 0); + } + } + } + } + req = (req + 1) % _nrOfRequest; + } + + /***************************************************************************************************************/ + // Have some out - queue some data to send + for (int i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].type == this->out) { + if (_fifos[i].iswfifo) { // WFIFO + // Start only a write process if there is really enough place in the outbound mailbox + if (_fifos[i].wfifo->dmaAllowed() + && _fifos[i].wfifo->used() > 0) { + uint32_t queue = _fifos[i].queue; + + // Can we send a message to this processor or is it blocked? + if (testMessage(queueOnSPE[queue]) <= 0) { + continue; + } + + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + } else { + uint32_t len = _fifos[i].wfifo->dmaStart(); + // Create a write-demand message + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_DEMAND, queue, len); + + request->len = len; + request->queue = queue; + request->status = read_request_sent; + request->wfifo = _fifos[i].wfifo; + request->iswfifo = true; + + sendMessage(message, queueOnSPE[queue]); + } + break; + } + } else { // FIFO + // Start only a write process if there is really enough place in the outbound mailbox + if (_fifos[i].fifo->dmaAllowed() && _fifos[i].fifo->used() + > 0) { + uint32_t queue = _fifos[i].queue; + + if (testMessage(queueOnSPE[queue]) <= 0) + continue; + + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + } else { + uint32_t len = _fifos[i].fifo->dmaStart(); + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_DEMAND, queue, len); + request->len = len; + request->queue = queue; + request->status = read_request_sent; + request->fifo = _fifos[i].fifo; + request->iswfifo = false; + + sendMessage(message, queueOnSPE[queue]); + } + break; + } + } + } + } + return true; +} + +/* + * Create a new request to store in the cache + * + */ +comRequest* FastCommunication::newRequest() { + for (int i = 0; i < _nrOfRequest; i++) { + if (!_request[i].valid) { + _request[i].tag_id = i + 3; + _request[i].valid = true; + return &(_request[i]); + } + } + + return NULL; +} + +/* + * Delete the request + * + */ +void FastCommunication::deleteRequest(comRequest* request) { + request->valid = false; + request->tag_id = 0; +} + +/* + * Returns the request one like to get + */ +comRequest* FastCommunication::getRequest(uint8_t status, uint32_t queue) { + uint8_t req = _currentRequest; + + for (int i = 0; i < _nrOfRequest; i++) { + if (_request[req].valid && _request[req].status == status + && _request[req].queue == queue) { + _currentRequest = (req + 1) % _nrOfRequest; + return &(_request[req]); + } + req = (req + 1) % _nrOfRequest; + } + + return NULL; +} + +/** + * True if no communication is necessary, false if there is active communication + */ +bool FastCommunication::empty() { ///////////////////////////////////////////////////////////////// + // check if one fifo would like to send something to another SPE + for (int i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].type == this->out) { + if (_fifos[i].iswfifo) { // WFIFO + if (_fifos[i].wfifo->used() > 0) + { + return false; + } + } else { + if (_fifos[i].fifo->used() > 0) + { + return false; + } + } + } + } + + // are open sendings? + for (int i = 0; i < _nrOfRequest; i++) { + if (_request[i].valid) { + return false; + } + } + + if (_nrSpeComplete < NUM_SPES) { + return false; + } + + return true; +} + +/** + * Send a message to a SPE + */ +void FastCommunication::sendMessage(uint32_t message, int32_t process) { + spe_in_mbox_write((spe_context*) _context_all[process], + (uint32_t*) &message, 1, SPE_MBOX_ANY_NONBLOCKING); +} + +/** + * Test if we can send a message to an SPE without stalling + */ +int FastCommunication::testMessage(int32_t process) { + return spe_in_mbox_status((spe_context*) _context_all[process]); +} + diff --git a/dol/src/dol/visitor/cell/lib/ppu/FastCommunication.h b/dol/src/dol/visitor/cell/lib/ppu/FastCommunication.h new file mode 100644 index 0000000..8ba7731 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/FastCommunication.h @@ -0,0 +1,118 @@ +/* + * Communication.h + * + * Created on: Mar 3, 2009 + * Author: lschor + */ + +#ifndef FASTCOMMUNICATION_H_ +#define FASTCOMMUNICATION_H_ + +// System includes +#include "cbe_mfc.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "lib/ppu/common_ppu.h" +#include "Fifo.h" +#include "WindowedFifo.h" +#include "../common.h" + + +// Include to allocate/free using for DMA transfers +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +//Cell Macros +#define waittag(tag_id) mfc_write_tag_mask(1< _size) { + return _size - _tail; + } else { + return _pos; + } +} + +/* + * Is allowed to start a dma request + */ +bool Fifo::dmaAllowed() { + if (_blocked > 0) { + _blocked--; + return false; + } else { + return !_activeDMA; + } +} diff --git a/dol/src/dol/visitor/cell/lib/ppu/Fifo.h b/dol/src/dol/visitor/cell/lib/ppu/Fifo.h new file mode 100644 index 0000000..8a06087 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/Fifo.h @@ -0,0 +1,45 @@ +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include +#include + +#include "../constant.h" + +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +/** + * FIFO Class: For remarks see SPE + */ +class Fifo { + public: + Fifo(unsigned size); + virtual ~Fifo(); + + virtual unsigned read(void *destination, unsigned len); + virtual unsigned write(const void *source, unsigned len); + + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + + virtual char *getQueuePointer(); + virtual void dmaRead(unsigned len); + virtual unsigned dmaStart(); + virtual bool dmaAllowed(); + + unsigned _inTail; + + protected: + char *_buffer; + + unsigned _tail; + unsigned _pos; + unsigned _size; + + unsigned _blocked; + bool _activeDMA; +}; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.cpp b/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.cpp new file mode 100644 index 0000000..fcd5f0e --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.cpp @@ -0,0 +1,66 @@ +#include "ProcessWrapper.h" + +/** + * + */ +ProcessWrapper::ProcessWrapper(char* name, int iteratorIndex[4]) { + //copy name, deliberately avoid using strlen and strcpy for code size + //minimization + int nameLength = 0; + while (name[nameLength] != 0) { + nameLength++; + } + _name = new char[nameLength + 1]; + for (int i = 0; i < nameLength; i++) { + _name[i] = name[i]; + } + + _isDetached = false; + for (int i = 0; i < 4; i++) { + _iteratorIndex[i] = iteratorIndex[i]; + } + + readPos = 0; + writePos = 0; +} + +/** + * + */ +ProcessWrapper::~ProcessWrapper() { + if (_name) { + delete _name; + } +} + +/** + * + */ +void ProcessWrapper::init() { + _process.init(&_process); +} + +/** + * + */ +int ProcessWrapper::fire() { + return _process.fire(&_process); +} + +/** + * + */ +void ProcessWrapper::detach() { + _isDetached = true; +} + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} diff --git a/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.h b/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.h new file mode 100644 index 0000000..0754af4 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/ProcessWrapper.h @@ -0,0 +1,32 @@ +#ifndef _PROCESSWRAPPER_H_ +#define _PROCESSWRAPPER_H_ + +#include + +/** + * Process Wrapper Class + */ +class ProcessWrapper +{ + public: + ProcessWrapper(char* name, int iteratorIndex[4]); + virtual ~ProcessWrapper(); + + virtual void init(); + virtual int fire(); + virtual bool isDetached() { return _isDetached; } + virtual void detach(); + virtual int getIndex(unsigned indexNumber) const; + + // Positions for read and write operations which are blocked by Protothread + unsigned int readPos; + unsigned int writePos; + + protected: + char* _name;; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; +}; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.cpp b/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.cpp new file mode 100644 index 0000000..16e09ba --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.cpp @@ -0,0 +1,223 @@ +#include "WindowedFifo.h" + +/** + * + */ +WindowedFifo::WindowedFifo(unsigned size = 20) { + //std::cout << "Create WindowedFifo." << std::endl; + _size = size; + _buffer = (char *) _malloc_align(_size, ALIGNMENT_FACTOR); + if(!_buffer) { + fprintf(stderr,"[WFIFO] Memory allocation failure\n"); + exit(-1); + } + + _head = 0; + _tail = 0; + + _headRoom = 0; + _tailRoom = 0; + _use = 0; + _blocked = 0; + + _isHeadReserved = false; + _isTailReserved = false; + _activeDMA = false; +} + +/** + * + */ +WindowedFifo::~WindowedFifo() { + if (_buffer) { + _free_align(_buffer); + } + _buffer = 0; + _head = 0; + _tail = 0; + _use = 0; +} + +/** + * + */ +unsigned WindowedFifo::reserve(void** dest, unsigned len) { + char** destination = (char**) dest; + //std::cout << "Attempt to reserve " << len << " bytes." << std::endl; + + //can only reserve one piece at a time + if (_isHeadReserved) { + *destination = 0; + return 0; + } + + //reserve at most as much memory as still available in the buffer + unsigned write = (len <= _size - _use ? len : _size - _use); + + if (write > 0) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_head + write > _size) { + write = _size - _head; + } + + _headRoom = (_head + write) == _size ? 0 : _head + write; + *destination = &(_buffer[_head]); + _isHeadReserved = true; + } + + //std::cout << "Reserved " << write << " bytes." << std::endl; + _writeReserve = write; + return write; +} + +/** + * + */ +void WindowedFifo::release() { + if (_isHeadReserved) { + //std::cout << "Released " << _headRoom - _head << " bytes." << std::endl; + _head = _headRoom; + _use += _writeReserve; + _isHeadReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::capture(void **dest, unsigned len) { + char** destination = (char**) dest; + //std::cout << "Attempt to capture " << len << " bytes." << std::endl; + + if (_isTailReserved) { + //std::cout << "Only one attempt to capture allowed." << std::endl; + *destination = 0; + return 0; + } + + //capture at most as much data as available in the buffer + unsigned read = (len <= _use ? len : _use); + + if (read > 0) { + //if wrap-around in buffer: return only buffer for the + //conntiguous buffer space + if (_tail + read > _size) { + read = _size - _tail; + } + + _tailRoom = (_tail + read) == _size ? 0 : _tailRoom = _tail + read; + *destination = &(_buffer[_tail]); + _isTailReserved = true; + } + + //std::cout << "Captured " << read << " bytes." << std::endl; + _readReserve = read; + return read; +} + +/** + * + */ +void WindowedFifo::consume() { + if (_isTailReserved) { + //std::cout << "Consumed " << _tailRoom - _tail << " bytes." << std::endl; + _tail = _tailRoom; + _use -= _readReserve; + _isTailReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::size() const { + return _size; +} + +/** + * + */ +unsigned WindowedFifo::unused() const { + return _size - _use; +} + +/** + * + */ +unsigned WindowedFifo::used() const { + return _use; +} + +/* + * Get the pointer to the start of the queue + */ +char *WindowedFifo::getQueuePointer() { + return _buffer; +} + +/* + * Has completed a dma read process, i.e. has read out of the queue + */ +void WindowedFifo::dmaRead(unsigned len) { + _activeDMA = false; + + if (len == 0) { + _blocked = BLOCKED_MAX_NR; + } else { + _tail = ((unsigned) (_tail + len) % _size); + _use -= len; + } +} + +/* + * Start a DMA request, returns the current space one have + */ +unsigned WindowedFifo::dmaStart() { + _activeDMA = true; + + // If we go over the end, we only take as much as is on one side! + //return used(); + + if (_tail + _use > _size) { + return _size - _tail; + } else { + return _use; + } +} + +/* + * Is allowed to start a dma request + */ +bool WindowedFifo::dmaAllowed() { +#ifndef STORE_REQUESTS + if (_blocked > 0) { + _blocked--; + return false; + } else { + return !_activeDMA; + } +#else + return !_activeDMA; +#endif +} + +/** + * Is needed for DMA transfers + */ +unsigned WindowedFifo::dmaWrite(const void *source, unsigned len) { + + char* buffer = (char*) source; + + if (_head + len < _size) { + memcpy(_buffer + _head, buffer, len); + } else { + // We should never be here! + memcpy(_buffer + _head, buffer, _size - _head); + memcpy(_buffer, buffer + _size - _head, len - _size + _head); + } + _use += len; + _head = (_head + len) >= _size ? _head + len - _size : _head + len; + + return len; +} diff --git a/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.h b/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.h new file mode 100644 index 0000000..2e94c46 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/WindowedFifo.h @@ -0,0 +1,61 @@ +#ifndef _WINDOWEDFIFO_H_ +#define _WINDOWEDFIFO_H_ + +#include +#include + +#include "../constant.h" + +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +class WindowedFifo { + public: + WindowedFifo(unsigned size); + virtual ~WindowedFifo(); + + // Write + virtual unsigned reserve(void** destination, unsigned len); + virtual void release(); + + // Read + virtual unsigned capture(void** destination, unsigned len); + virtual void consume(); + + // General FIFO functions + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + + // DMA functions + virtual char *getQueuePointer(); + virtual void dmaRead(unsigned len); + virtual unsigned dmaStart(); + virtual bool dmaAllowed(); + virtual unsigned dmaWrite(const void *source, unsigned len); + + // Global variables + unsigned _inTail; + protected: + char *_buffer; + + unsigned _head; + unsigned _tail; + unsigned _headRoom; + unsigned _tailRoom; + + unsigned _size; + unsigned _use; + + unsigned _writeReserve; + unsigned _readReserve; + + bool _isHeadReserved; + bool _isTailReserved; + + unsigned _blocked; // Blocked number the request has to wait + bool _activeDMA; // Is there an active DMA on this buffer + +}; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/ppu/common.cpp b/dol/src/dol/visitor/cell/lib/ppu/common.cpp new file mode 100644 index 0000000..bdaf7c8 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/common.cpp @@ -0,0 +1,28 @@ +/* + * common.cpp + * + * Created on: Feb 27, 2009 + * Author: lschor + */ + +#include "../common.h" + +/** + * Aligend a number to to the data bus + */ +uint32_t roundDMA(uint32_t number) +{ + if (number > 16) + if (number % 16 == 0) return number; + else return number + 16 - (number % 16); + else if (number > 8) + return 16; + else if (number > 4) + return 8; + else if (number > 2) + return 4; + else if (number > 1) + return 2; + else + return 1; +} diff --git a/dol/src/dol/visitor/cell/lib/ppu/common_ppu.h b/dol/src/dol/visitor/cell/lib/ppu/common_ppu.h new file mode 100644 index 0000000..09a8d78 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/common_ppu.h @@ -0,0 +1,32 @@ +/**************************************************************** + * Common structs for the PPU + * Creator: lschor, 2008-11-21 + * Description: Common structs for the PPU + * + * Revision: + * - 2008-11-21: Created + */ + +#ifndef __COMMON_PPU_H__ +#define __COMMON_PPU_H__ + +#include + +// data structure for running SPE thread +typedef struct spu_data { + spe_context_ptr_t spe_ctx; + pthread_t pthread; + void *argp; +} spu_data_t; + +// Struct for a process +typedef struct _process_data { + uint64_t *procContentsAll; + int32_t *queueFromSPU; + int32_t *queueOnSPU; + uint64_t *ea_ls_base; + uint64_t *fifoTails; +} ProcessData; + + +#endif diff --git a/dol/src/dol/visitor/cell/lib/ppu/dolSupport.cpp b/dol/src/dol/visitor/cell/lib/ppu/dolSupport.cpp new file mode 100644 index 0000000..3c6d36d --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/dolSupport.cpp @@ -0,0 +1,98 @@ +#include "dolSupport.h" + +/** + * + */ +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p) { + unsigned int + pos = + static_cast ((static_cast (p->wptr))->wrapper)->readPos; + pos += ((Fifo*) fifo)->read((char *) buf + pos, len - pos); + + if (pos == len) + static_cast ((static_cast (p->wptr))->wrapper)->readPos + = 0; + else + static_cast ((static_cast (p->wptr))->wrapper)->readPos + = pos; + return pos; +} + +/** + * + */ +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p) { + unsigned int + pos = + static_cast ((static_cast (p->wptr))->wrapper)->writePos; + pos += ((Fifo*) fifo)->write((char *) buf + pos, len - pos); + + if (pos == len) + static_cast ((static_cast (p->wptr))->wrapper)->writePos + = 0; + else + static_cast ((static_cast (p->wptr))->wrapper)->writePos + = pos; + return pos; +} + +/** + * + */ +unsigned reserve(void* fifo, void** destination, unsigned len, + DOLProcess* p) { + return ((WindowedFifo*) fifo)->reserve(destination, len); +} + +/** + * + */ +void release(void* fifo, DOLProcess* p) { + ((WindowedFifo*) fifo)->release(); +} + +/** + * + */ +unsigned capture(void* fifo, void** destination, unsigned len, + DOLProcess* p) { + return ((WindowedFifo*) fifo)->capture(destination, len); +} + +/** + * + */ +void consume(void* fifo, DOLProcess* p) { + ((WindowedFifo*) fifo)->consume(); +} + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast ((static_cast (p->wptr))->wrapper)->detach(); +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0) { + *port = (void**) ((void**) base)[index0]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1) { + *port = (void**) ((void**) base)[index0 * range1 + index1]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1, int index2, + int range2) { + *port = (void**) ((void**) base)[index0 * range1 * range2 + index1 + * range2 + index2]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1, int index2, + int range2, int index3, int range3) { + *port = (void**) ((void**) base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + index2 * range3 + index3]; +} diff --git a/dol/src/dol/visitor/cell/lib/ppu/dolSupport.h b/dol/src/dol/visitor/cell/lib/ppu/dolSupport.h new file mode 100644 index 0000000..14a1c55 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu/dolSupport.h @@ -0,0 +1,78 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include "dol.h" +#include "pt.h" + +#include "ProcessWrapper.h" + +#include "Fifo.h" +#include "WindowedFifo.h" + +typedef struct _process_data { + int lc; + ProcessWrapper *wrapper; +} process_data; + + +#define DOL_read(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), read(port, buf, size, process) == size); + +#define DOL_write(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), write(port, buf, size, process) == size); + +#define DOL_reserve(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), reserve(port, (void**)buf, size, process) == size); + +#define DOL_release(port, process) \ + release(port, process); + +#define DOL_capture(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), capture(port, (void**)buf, size, process) == size); + +#define DOL_consume(port, process) \ + consume(port, process); + +void DOL_detach(DOLProcess* p); + +//macros to deal with iterated ports +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) static Fifo *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#define GETINDEX(dimension) \ + static_cast((static_cast(p->wptr))->wrapper)->getIndex(dimension) + +void createPort(void** port, void* base, int number_of_indices, int index0, int range0); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2, int index3, int range3); + +//fifo access functions +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p); +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p); + +//windowed fifo access functions +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p); +void release(void* fifo, DOLProcess* p); +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p); +void consume(void* fifo, DOLProcess* p); + +#endif diff --git a/dol/src/dol/visitor/cell/lib/ppu_main.h b/dol/src/dol/visitor/cell/lib/ppu_main.h new file mode 100644 index 0000000..3b80e8a --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/ppu_main.h @@ -0,0 +1,43 @@ +/**************************************************************** + * Header for the main function + * Creator: lschor, 2008-11-21 + * Description: Header file for the main function of the PPU + * + * Revision: + * - 2008-11-21: Created + */ + +#ifndef __PPU_MAIN_H__ +#define __PPU_MAIN_H__ + +// System includes +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "lib/malloc_align.h" +#include "lib/free_align.h" +#include "lib/common.h" +#include "lib/estimation.h" +#include "lib/ppu/common_ppu.h" +#include "cbe_mfc.h" + +// Program context for the Processes +volatile process_context ctx_proc[NUM_PROCS_SPU] __attribute__ ((aligned(16))); + +// Program context for the SPEs +volatile spu_context ctx_spu[NUM_SPES] __attribute__ ((aligned(16))); + +// The SPE-program-handler +spe_program_handle_t *program[NUM_SPES]; + +// Data for the SPEs +spu_data_t data[NUM_SPES]; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/pt/lc-addrlabels.h b/dol/src/dol/visitor/cell/lib/pt/lc-addrlabels.h new file mode 100644 index 0000000..b75f4e7 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/pt/lc-addrlabels.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: lc-addrlabels.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on the "Labels as + * values" feature of gcc + * \author + * Adam Dunkels + * + * This implementation of local continuations is based on a special + * feature of the GCC C compiler called "labels as values". This + * feature allows assigning pointers with the address of the code + * corresponding to a particular C label. + * + * For more information, see the GCC documentation: + * http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html + * + */ + +#ifndef __LC_ADDRLABELS_H__ +#define __LC_ADDRLABELS_H__ + +/** \hideinitializer */ +typedef void * lc_t; + +#define LC_INIT(s) s = NULL + +#define LC_RESUME(s) \ + do { \ + if(s != NULL) { \ + goto *s; \ + } \ + } while(0) + +#define LC_CONCAT2(s1, s2) s1##s2 +#define LC_CONCAT(s1, s2) LC_CONCAT2(s1, s2) + +#define LC_SET(s) \ + do { \ + LC_CONCAT(LC_LABEL, __LINE__): \ + (s) = &&LC_CONCAT(LC_LABEL, __LINE__); \ + } while(0) + +#define LC_END(s) + +#endif /* __LC_ADDRLABELS_H__ */ +/** @} */ diff --git a/dol/src/dol/visitor/cell/lib/pt/lc-switch.h b/dol/src/dol/visitor/cell/lib/pt/lc-switch.h new file mode 100644 index 0000000..e47085c --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/pt/lc-switch.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: lc-switch.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on switch() statment + * \author Adam Dunkels + * + * This implementation of local continuations uses the C switch() + * statement to resume execution of a function somewhere inside the + * function's body. The implementation is based on the fact that + * switch() statements are able to jump directly into the bodies of + * control structures such as if() or while() statmenets. + * + * This implementation borrows heavily from Simon Tatham's coroutines + * implementation in C: + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + */ + +#ifndef __LC_SWITCH_H__ +#define __LC_SWITCH_H__ + +/* WARNING! lc implementation using switch() does not work if an + LC_SET() is done within another switch() statement! */ + +/** \hideinitializer */ +typedef unsigned short lc_t; + +#define LC_INIT(s) s = 0; + +#define LC_RESUME(s) switch(s) { case 0: + +#define LC_SET(s) s = __LINE__; case __LINE__: + +#define LC_END(s) } + +#endif /* __LC_SWITCH_H__ */ + +/** @} */ diff --git a/dol/src/dol/visitor/cell/lib/pt/lc.h b/dol/src/dol/visitor/cell/lib/pt/lc.h new file mode 100644 index 0000000..9ef2f0f --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/pt/lc.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the protothreads library. + * + * Author: Adam Dunkels + * + * $Id: lc.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \defgroup lc Local continuations + * @{ + * + * Local continuations form the basis for implementing protothreads. A + * local continuation can be set in a specific function to + * capture the state of the function. After a local continuation has + * been set can be resumed in order to restore the state of the + * function at the point where the local continuation was set. + * + * + */ + +/** + * \file lc.h + * Local continuations + * \author + * Adam Dunkels + * + */ + +#ifdef DOXYGEN +/** + * Initialize a local continuation. + * + * This operation initializes the local continuation, thereby + * unsetting any previously set continuation state. + * + * \hideinitializer + */ +#define LC_INIT(lc) + +/** + * Set a local continuation. + * + * The set operation saves the state of the function at the point + * where the operation is executed. As far as the set operation is + * concerned, the state of the function does not include the + * call-stack or local (automatic) variables, but only the program + * counter and such CPU registers that needs to be saved. + * + * \hideinitializer + */ +#define LC_SET(lc) + +/** + * Resume a local continuation. + * + * The resume operation resumes a previously set local continuation, thus + * restoring the state in which the function was when the local + * continuation was set. If the local continuation has not been + * previously set, the resume operation does nothing. + * + * \hideinitializer + */ +#define LC_RESUME(lc) + +/** + * Mark the end of local continuation usage. + * + * The end operation signifies that local continuations should not be + * used any more in the function. This operation is not needed for + * most implementations of local continuation, but is required by a + * few implementations. + * + * \hideinitializer + */ +#define LC_END(lc) + +/** + * \var typedef lc_t; + * + * The local continuation type. + * + * \hideinitializer + */ +#endif /* DOXYGEN */ + +#ifndef __LC_H__ +#define __LC_H__ + + +#ifdef LC_INCLUDE +#include LC_INCLUDE +#else +#include "lc-switch.h" +#endif /* LC_INCLUDE */ + +#endif /* __LC_H__ */ + +/** @} */ +/** @} */ diff --git a/dol/src/dol/visitor/cell/lib/pt/pt-sem.h b/dol/src/dol/visitor/cell/lib/pt/pt-sem.h new file mode 100644 index 0000000..a6e1428 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/pt/pt-sem.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the protothreads library. + * + * Author: Adam Dunkels + * + * $Id: pt-sem.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \defgroup ptsem Protothread semaphores + * @{ + * + * This module implements counting semaphores on top of + * protothreads. Semaphores are a synchronization primitive that + * provide two operations: "wait" and "signal". The "wait" operation + * checks the semaphore counter and blocks the thread if the counter + * is zero. The "signal" operation increases the semaphore counter but + * does not block. If another thread has blocked waiting for the + * semaphore that is signalled, the blocked thread will become + * runnable again. + * + * Semaphores can be used to implement other, more structured, + * synchronization primitives such as monitors and message + * queues/bounded buffers (see below). + * + * The following example shows how the producer-consumer problem, also + * known as the bounded buffer problem, can be solved using + * protothreads and semaphores. Notes on the program follow after the + * example. + * + \code +#include "pt-sem.h" + +#define NUM_ITEMS 32 +#define BUFSIZE 8 + +static struct pt_sem mutex, full, empty; + +PT_THREAD(producer(struct pt *pt)) +{ + static int produced; + + PT_BEGIN(pt); + + for(produced = 0; produced < NUM_ITEMS; ++produced) { + + PT_SEM_WAIT(pt, &full); + + PT_SEM_WAIT(pt, &mutex); + add_to_buffer(produce_item()); + PT_SEM_SIGNAL(pt, &mutex); + + PT_SEM_SIGNAL(pt, &empty); + } + + PT_END(pt); +} + +PT_THREAD(consumer(struct pt *pt)) +{ + static int consumed; + + PT_BEGIN(pt); + + for(consumed = 0; consumed < NUM_ITEMS; ++consumed) { + + PT_SEM_WAIT(pt, &empty); + + PT_SEM_WAIT(pt, &mutex); + consume_item(get_from_buffer()); + PT_SEM_SIGNAL(pt, &mutex); + + PT_SEM_SIGNAL(pt, &full); + } + + PT_END(pt); +} + +PT_THREAD(driver_thread(struct pt *pt)) +{ + static struct pt pt_producer, pt_consumer; + + PT_BEGIN(pt); + + PT_SEM_INIT(&empty, 0); + PT_SEM_INIT(&full, BUFSIZE); + PT_SEM_INIT(&mutex, 1); + + PT_INIT(&pt_producer); + PT_INIT(&pt_consumer); + + PT_WAIT_THREAD(pt, producer(&pt_producer) & + consumer(&pt_consumer)); + + PT_END(pt); +} + \endcode + * + * The program uses three protothreads: one protothread that + * implements the consumer, one thread that implements the producer, + * and one protothread that drives the two other protothreads. The + * program uses three semaphores: "full", "empty" and "mutex". The + * "mutex" semaphore is used to provide mutual exclusion for the + * buffer, the "empty" semaphore is used to block the consumer is the + * buffer is empty, and the "full" semaphore is used to block the + * producer is the buffer is full. + * + * The "driver_thread" holds two protothread state variables, + * "pt_producer" and "pt_consumer". It is important to note that both + * these variables are declared as static. If the static + * keyword is not used, both variables are stored on the stack. Since + * protothreads do not store the stack, these variables may be + * overwritten during a protothread wait operation. Similarly, both + * the "consumer" and "producer" protothreads declare their local + * variables as static, to avoid them being stored on the stack. + * + * + */ + +/** + * \file + * Couting semaphores implemented on protothreads + * \author + * Adam Dunkels + * + */ + +#ifndef __PT_SEM_H__ +#define __PT_SEM_H__ + +#include "pt.h" + +struct pt_sem { + unsigned int count; +}; + +/** + * Initialize a semaphore + * + * This macro initializes a semaphore with a value for the + * counter. Internally, the semaphores use an "unsigned int" to + * represent the counter, and therefore the "count" argument should be + * within range of an unsigned int. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \param c (unsigned int) The initial count of the semaphore. + * \hideinitializer + */ +#define PT_SEM_INIT(s, c) (s)->count = c + +/** + * Wait for a semaphore + * + * This macro carries out the "wait" operation on the semaphore. The + * wait operation causes the protothread to block while the counter is + * zero. When the counter reaches a value larger than zero, the + * protothread will continue. + * + * \param pt (struct pt *) A pointer to the protothread (struct pt) in + * which the operation is executed. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \hideinitializer + */ +#define PT_SEM_WAIT(pt, s) \ + do { \ + PT_WAIT_UNTIL(pt, (s)->count > 0); \ + --(s)->count; \ + } while(0) + +/** + * Signal a semaphore + * + * This macro carries out the "signal" operation on the semaphore. The + * signal operation increments the counter inside the semaphore, which + * eventually will cause waiting protothreads to continue executing. + * + * \param pt (struct pt *) A pointer to the protothread (struct pt) in + * which the operation is executed. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \hideinitializer + */ +#define PT_SEM_SIGNAL(pt, s) ++(s)->count + +#endif /* __PT_SEM_H__ */ + +/** @} */ +/** @} */ + diff --git a/dol/src/dol/visitor/cell/lib/pt/pt.h b/dol/src/dol/visitor/cell/lib/pt/pt.h new file mode 100644 index 0000000..a834a13 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/pt/pt.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: pt.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \file + * Protothreads implementation. + * \author + * Adam Dunkels + * + */ + +#ifndef __PT_H__ +#define __PT_H__ + +#include "lc.h" + +typedef struct _pt { + lc_t lc; +} pt; + +#define PT_WAITING 0 +#define PT_YIELDED 1 +#define PT_EXITED 2 +#define PT_ENDED 3 + +/** + * \name Initialization + * @{ + */ + +/** + * Initialize a protothread. + * + * Initializes a protothread. Initialization must be done prior to + * starting to execute the protothread. + * + * \param pt A pointer to the protothread control structure. + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_INIT(pt) LC_INIT((pt)->lc) + +/** @} */ + +/** + * \name Declaration and definition + * @{ + */ + +/** + * Declaration of a protothread. + * + * This macro is used to declare a protothread. All protothreads must + * be declared with this macro. + * + * \param name_args The name and arguments of the C function + * implementing the protothread. + * + * \hideinitializer + */ +#define PT_THREAD(name_args) char name_args + +/** + * Declare the start of a protothread inside the C function + * implementing the protothread. + * + * This macro is used to declare the starting point of a + * protothread. It should be placed at the start of the function in + * which the protothread runs. All C statements above the PT_BEGIN() + * invokation will be executed each time the protothread is scheduled. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) + +/** + * Declare the end of a protothread. + * + * This macro is used for declaring that a protothread ends. It must + * always be used together with a matching PT_BEGIN() macro. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ + PT_INIT(pt); return PT_ENDED; } + +/** @} */ + +/** + * \name Blocked wait + * @{ + */ + +/** + * Block and wait until condition is true. + * + * This macro blocks the protothread until the specified condition is + * true. + * + * \param pt A pointer to the protothread control structure. + * \param condition The condition. + * + * \hideinitializer + */ +#define PT_WAIT_UNTIL(pt, condition) \ + do {/*printf("Test1: %d, %s\n",__LINE__,__FILE__);*/ \ + LC_SET((pt)->lc); /*printf("Test2: %d\n",(pt)->lc);*/ \ + if(!(condition)) { \ + return PT_WAITING; \ + } \ + } while(0) + +/** + * Block and wait while condition is true. + * + * This function blocks and waits while condition is true. See + * PT_WAIT_UNTIL(). + * + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * \hideinitializer + */ +#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) + +/** @} */ + +/** + * \name Hierarchical protothreads + * @{ + */ + +/** + * Block and wait until a child protothread completes. + * + * This macro schedules a child protothread. The current protothread + * will block until the child protothread completes. + * + * \note The child protothread must be manually initialized with the + * PT_INIT() function before this function is used. + * + * \param pt A pointer to the protothread control structure. + * \param thread The child protothread with arguments + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) + +/** + * Spawn a child protothread and wait until it exits. + * + * This macro spawns a child protothread and waits until it exits. The + * macro can only be used within a protothread. + * + * \param pt A pointer to the protothread control structure. + * \param child A pointer to the child protothread's control structure. + * \param thread The child protothread with arguments + * + * \hideinitializer + */ +#define PT_SPAWN(pt, child, thread) \ + do { \ + PT_INIT((child)); \ + PT_WAIT_THREAD((pt), (thread)); \ + } while(0) + +/** @} */ + +/** + * \name Exiting and restarting + * @{ + */ + +/** + * Restart the protothread. + * + * This macro will block and cause the running protothread to restart + * its execution at the place of the PT_BEGIN() call. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_RESTART(pt) \ + do { \ + PT_INIT(pt); \ + return PT_WAITING; \ + } while(0) + +/** + * Exit the protothread. + * + * This macro causes the protothread to exit. If the protothread was + * spawned by another protothread, the parent protothread will become + * unblocked and can continue to run. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_EXIT(pt) \ + do { \ + PT_INIT(pt); \ + return PT_EXITED; \ + } while(0) + +/** @} */ + +/** + * \name Calling a protothread + * @{ + */ + +/** + * Schedule a protothread. + * + * This function shedules a protothread. The return value of the + * function is non-zero if the protothread is running or zero if the + * protothread has exited. + * + * \param f The call to the C function implementing the protothread to + * be scheduled + * + * \hideinitializer + */ +#define PT_SCHEDULE(f) ((f) < PT_EXITED) + +/** @} */ + +/** + * \name Yielding from a protothread + * @{ + */ + +/** + * Yield from the current protothread. + * + * This function will yield the protothread, thereby allowing other + * processing to take place in the system. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_YIELD(pt) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if(PT_YIELD_FLAG == 0) { \ + return PT_YIELDED; \ + } \ + } while(0) + +/** + * \brief Yield from the protothread until a condition occurs. + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * This function will yield the protothread, until the + * specified condition evaluates to true. + * + * + * \hideinitializer + */ +#define PT_YIELD_UNTIL(pt, cond) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if((PT_YIELD_FLAG == 0) || !(cond)) { \ + return PT_YIELDED; \ + } \ + } while(0) + +/** @} */ + +#endif /* __PT_H__ */ + +/** @} */ diff --git a/dol/src/dol/visitor/cell/lib/spu/FastCommunication.cpp b/dol/src/dol/visitor/cell/lib/spu/FastCommunication.cpp new file mode 100644 index 0000000..2f2c3de --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/FastCommunication.cpp @@ -0,0 +1,707 @@ +/* + * FastCommunication.cpp + * + * Created on: Mar 3, 2009 + * Author: lschor + * + * Provides the communication between various processors. + * + * + * + * Description: + * + * Sender: The processor now has some data that need to be forwarded + * to another processor. + * Receiver: The processor that needs to get the data. + * + * Sender ---- * + * -----> Have some data * + * (Len, queue) * + * -----------> Receiver * + * * + * Check its len * + * * + * Sets up a DMA * + * transfer to * + * read from the * + * <-------------- queue to a * + * MFC performs the local buffer * + * transfer (Alignment) * + * <--------- * + * * + * * + * (Pools if the * + * transfer is * + * completed) * + * * + * * + * Copy the data * + * from its temp. * + * buffer to the * + * queue und * + * Have completed <------------ informs the * + * <---------- (queue, len) sender * + * Can increase the * + * pointers in the FIFO + * + * + * Has two ways to handling request, which are not possible + * to work out currently: + * 1) Send back "len = 0" + * --> Needs to send more messages, but may bigger lens + * 2) Store them until you have enough space to read + * --> Smaller lens and less messages + */ + +#include "FastCommunication.h" + +/* + * Constructor + */ +FastCommunication::FastCommunication(int nrOfQueues, uint64_t ea_base, + uint64_t *ea_base_all, int32_t * queueFromSPEIn, + int32_t * queueOnSPEIn, uint64_t *fifoTails) { + + // Set the base address + _ea_base = ea_base; + + _ea_base_all = ea_base_all; + queueFromSPE = queueFromSPEIn; + queueOnSPE = queueOnSPEIn; + _fifoTails = fifoTails; + + _nrOfQueues = nrOfQueues; + try { _fifos = new fifoCollection[nrOfQueues]; } + catch(std::bad_alloc &e) { + fprintf(stderr, "[FastCommunication] Memory allocation failure\n"); + exit(1); + } + _nrOfRequest = 0; + + for (int i = 0; i < MAXNOREQ; i++) { + _request[i].valid = false; + } +} + +/* + * Deconstructor + */ +FastCommunication::~FastCommunication() { + delete _fifos; +} + +/* + * Register an additional FIFO in the Communication + */ +bool FastCommunication::addFifo(int fifoNr, Fifo* fifo, int type, + int queue) { + _fifos[fifoNr].fifo = fifo; + _fifos[fifoNr].queue = queue; + _fifos[fifoNr].type = type; + _fifos[fifoNr].iswfifo = false; + + // Per FIFO queue, we need two requests, but we store at max MAXNOREQ of them + if (_nrOfRequest < MAXNOREQ) { + _nrOfRequest += 2; + } + + return true; +} + +/* + * Register an additional WindowedFIFO in the Communication + */ +bool FastCommunication::addWFifo(int fifoNr, WindowedFifo* wfifo, + int type, int queue) { + _fifos[fifoNr].wfifo = wfifo; + _fifos[fifoNr].queue = queue; + _fifos[fifoNr].type = type; + _fifos[fifoNr].iswfifo = true; + + // Per FIFO queue, we need two requests, but we store at max MAXNOREQ of them + if (_nrOfRequest < MAXNOREQ) { + _nrOfRequest += 2; + } + + return true; +} + +/* + * Communication: Main update procedure to update + */ +bool FastCommunication::update() { + // Check if you have received a new message + while (spu_stat_in_mbox() > 0) { + uint32_t messageIn = spu_read_in_mbox(); + + uint32_t code = GETFASTCODE(messageIn); + uint32_t queue = GETFASTQUEUE(messageIn); + uint32_t len = GETFASTLEN(messageIn); + + /***************************************************************************************************************/ + if (code == SPE_READ_DEMAND) // One should start a read process + { + // Find out for which fifo the request is + fifoCollection *fifocol = NULL; + int i = 0; + for (i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].queue == queue) { + fifocol = &_fifos[i]; + break; + } + } + + // The queue was not found + if (fifocol == NULL) { + printf("SPU COM> ERROR, this queue does not exists!\n"); + return false; + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Windowed FIFO + if (_fifos[i].iswfifo) { + + WindowedFifo* wfifo = NULL; + wfifo = _fifos[i].wfifo; + + // Find the current tail of the queue from where to read + uint32_t inTail = wfifo->_inTail; + + // This is the len we like to read + len = len > (wfifo->unused()) ? wfifo->unused() : len; + + // We can read something + if (len > 0) { + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + // reserve DMA tag ID + uint32_t tag_id; + if ((tag_id = mfc_tag_reserve()) == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return false; + } + + // Generate the base address of the input FIFO + uint64_t baseAddress = _fifoTails[queue] + inTail; + + // Align the address to the correct factor (in general 128 or 32) + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset --> How much we had to align + uint32_t offset = _fifoTails[queue] + inTail + - baseAddress; + + request->data = (char *) _malloc_align(roundDMA(len + + offset), ALIGNMENT_FACTOR); + + // Store all data in the request buffer + request->len = len; + + request->wfifo = wfifo; + request->iswfifo = true; + + request->status = read_started; + request->queue = queue; + request->offset = offset; + request->tag_id = tag_id; + + // Set up the request in the MFC + mfc_get((void *) &(request->data[0]), baseAddress, + roundDMA(len + offset), tag_id, 0, 0); + } + + else { +#ifndef STORE_REQUESTS // Send len = 0 back to the sender, this one should try it in a later phase + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); +#else // I store the request and may try in a later time to start the transfer + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) + { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE(SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + request->len = len; + request->wfifo = wfifo; + request->iswfifo = true; + request->status = read_pending; + request->queue = queue; +#endif + } + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Classic FIFO + else { + Fifo* fifo = NULL; + fifo = _fifos[i].fifo; + + uint32_t inTail = fifo->_inTail; + + // This is the len we like to read + len = len > (fifo->unused()) ? fifo->unused() : len; + + // We can read something + if (len > 0) { + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + uint32_t tag_id; + + // reserve DMA tag ID + if ((tag_id = mfc_tag_reserve()) == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return false; + } + + uint64_t baseAddress = _fifoTails[queue] + inTail; + + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset + uint32_t offset = _fifoTails[queue] + inTail + - baseAddress; + + // Store all data in the request buffer + request->data = (char *) _malloc_align(roundDMA(len + + offset), ALIGNMENT_FACTOR); + request->len = len; + request->fifo = fifo; + request->iswfifo = false; + request->status = read_started; + request->queue = queue; + request->offset = offset; + request->tag_id = tag_id; + + // Tell the fifo that one has reserved some data + + mfc_get((void *) &(request->data[0]), baseAddress, + roundDMA(len + offset), tag_id, 0, 0); + } + + // len == 0 + else { +#ifndef STORE_REQUESTS // Send len = 0 back to the sender, this one should try it in a later phase + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); +#else // I store the request and may try in a later time to start the transfer + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) + { + // not possible to start a request for this SPE --> Send len = 0 + uint32_t message = CREATEFASTMESSAGE(SPE_READ_COMPLETE, queue, 0); + sendMessage(message, queueFromSPE[queue]); + return false; + } + + request->len = len; + request->fifo = fifo; + request->iswfifo = false; + request->status = read_pending; + request->queue = queue; +#endif + } + } + } + + /***************************************************************************************************************/ + else if (code == SPE_READ_COMPLETE) // A read has finished + { + // Get the stored request + comRequest* request = getRequest(read_request_sent, queue); + if (request == NULL) { + printf( + ">>>>>>>>>>>>>>>>> Communicate SPU> Couldn't find the request\n"); + return 0; + } + + // Inform my FIFO that the request is completed + if (request->iswfifo) + request->wfifo->dmaRead(len); + else + request->fifo->dmaRead(len); + + // Request free + deleteRequest(request); + } + } + + /***************************************************************************************************************/ + // Check if some active write processes have finished + uint8_t req = _currentRequest; + + for (int i = 0; i < _nrOfRequest; i++) // Check all possible requests + { + if (_request[req].valid) // Only to check if the request is valid + { + if (_request[req].status == read_started) { // We have setup the request, now check if it is complete + + // If I cannot send a message to the corresponding processor --> Do not have to check it + if (!(testMessage(queueOnSPE[_request[req].queue]) > 0)) + continue; + + // Check if the specific Tag-ID has completed + uint32_t tag_id = _request[req].tag_id; + mfc_write_tag_mask(1 << tag_id); + mfc_write_tag_update(MFC_TAG_UPDATE_IMMEDIATE); + uint32_t ret = mfc_read_tag_status(); + + if (!((ret & (1 << tag_id)) == 0)) { + + // This update is finished + mfc_tag_release(tag_id); + + // Have to write the data into the fifo + if (_request[req].iswfifo) { // WFIFO + + _request[req].wfifo->dmaWrite( + (char *) _request[req].data + + _request[req].offset, + _request[req].len); + _free_align(_request[req].data); + + // Increase the inTail value (used to know where the last request was started) + _request[req].wfifo->_inTail + = (_request[req].wfifo->_inTail + + _request[req].len) % (FIFO_SIZE[_request[req].queue]); + } else { // FIFO + _request[req].fifo->write( + (char *) _request[req].data + + _request[req].offset, + _request[req].len); + _free_align(_request[req].data); + + _request[req].fifo->_inTail + = (_request[req].fifo->_inTail + + _request[req].len) % (FIFO_SIZE[_request[req].queue]); + + } + + // Inform the sender about the event + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_COMPLETE, _request[req].queue, + _request[req].len); + sendMessage(message, queueFromSPE[_request[req].queue]); + + // Delete the request + deleteRequest(&_request[req]); + _currentRequest = (req + 1) % _nrOfRequest; + + break; // continue is also working + } + } else if (_request[req].status == read_pending) { // Has an open request for sending + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Windowed FIFO + if (_request[req].iswfifo) { + + if (_request[req].wfifo->unused() > 0) { + comRequest *request = &_request[req]; + + // This is the len we like to read + uint32_t len = request->len > (request->wfifo->unused()) ? request->wfifo->unused() + : request->len; + + // reserve DMA tag ID + uint32_t tag_id; + if ((tag_id = mfc_tag_reserve()) + == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return false; + } + + uint32_t inTail = request->wfifo->_inTail; + uint64_t baseAddress = _fifoTails[request->queue] + + inTail; + + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset --> How much we had to align + uint32_t offset = _fifoTails[request->queue] + + inTail - baseAddress; + + request->data = (char *) _malloc_align(roundDMA( + len + offset), ALIGNMENT_FACTOR); + request->len = len; + request->status = read_started; + request->offset = offset; + request->tag_id = tag_id; + + // Set up the request in the MFC + mfc_get((void *) &(request->data[0]), baseAddress, + roundDMA(len + offset), tag_id, 0, 0); + } + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Classic FIFO + else { + + if (_request[req].fifo->unused() > 0) { + comRequest *request = &_request[req]; + + // This is the len we like to read + uint32_t len = request->len > (request->fifo->unused()) ? request->fifo->unused() + : request->len; + + uint32_t tag_id; + // reserve DMA tag ID + if ((tag_id = mfc_tag_reserve()) + == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return false; + } + + uint32_t inTail = request->fifo->_inTail; + uint64_t baseAddress = _fifoTails[request->queue] + inTail; + + while (baseAddress % ALIGNMENT_FACTOR_POWER2 != 0) + baseAddress--; + + // Offset + uint32_t offset = _fifoTails[request->queue] + + inTail - baseAddress; + + // Store all data in the request buffer + request->data = (char *) _malloc_align(roundDMA( + len + offset), ALIGNMENT_FACTOR); + request->len = len; + request->status = read_started; + request->offset = offset; + request->tag_id = tag_id; + + // Start the DMA transfer + mfc_get((void *) &(request->data[0]), baseAddress, + roundDMA(len + offset), tag_id, 0, 0); + } + } + } + } + // Go to the next Request + req = (req + 1) % _nrOfRequest; + } + + /***************************************************************************************************************/ + // Start a new request to the receiver of a FIFO + for (int i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].type == this->out) // Only out-FIFO have to be quecked + { + if (_fifos[i].iswfifo) { // WFIFO + + // Start only a write process if there is really enough place in the outbound mailbox + if (_fifos[i].wfifo->dmaAllowed() && _fifos[i].wfifo->used() > 0) { + uint32_t queue = _fifos[i].queue; + + // Can we send a message to this processor or is it blocked? + if (testMessage(queueOnSPE[queue]) <= 0) { + continue; + } + + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + } else { + uint32_t len = _fifos[i].wfifo->dmaStart(); + // Create a write-demand message + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_DEMAND, queue, len); + + request->len = len; + request->queue = queue; + request->status = read_request_sent; + request->wfifo = _fifos[i].wfifo; + request->iswfifo = true; + + sendMessage(message, queueOnSPE[queue]); + } + break; + } + } else { // FIFO + + // Start only a write process if there is really enough place in the outbound mailbox + if (_fifos[i].fifo->dmaAllowed() && _fifos[i].fifo->used() > 0) { + uint32_t queue = _fifos[i].queue; + + // Check if we can send a message to the processor + if (testMessage(queueOnSPE[queue]) <= 0) { + continue; + } + + // Write all information we used to the request-memory + comRequest* request = newRequest(); + + // Has no memory to store a new request + if (request == NULL) { + } else { + uint32_t len = _fifos[i].fifo->dmaStart(); + // Create a write-demand message + uint32_t message = CREATEFASTMESSAGE( + SPE_READ_DEMAND, queue, len); + + request->len = len; + request->queue = queue; + request->status = read_request_sent; + request->fifo = _fifos[i].fifo; + request->iswfifo = false; + + sendMessage(message, queueOnSPE[queue]); + } + break; + } + } + } + } + return true; +} + +/* + * Create a new request to store in the cache + * + */ +comRequest* FastCommunication::newRequest() { + for (int i = 0; i < _nrOfRequest; i++) { + if (!_request[i].valid) { + _request[i].valid = true; + return &(_request[i]); + } + } + + return NULL; +} + +/* + * Delete the request + * + */ +void FastCommunication::deleteRequest(comRequest* request) { + request->valid = false; +} + +/* + * Returns the request one like to get + */ +comRequest* FastCommunication::getRequest(uint8_t status, uint32_t queue) { + uint8_t req = _currentRequest; + + for (int i = 0; i < _nrOfRequest; i++) { + if (_request[req].valid && _request[req].status == status + && _request[req].queue == queue) { + _currentRequest = (req + 1) % _nrOfRequest; + return &(_request[req]); + } + req = (req + 1) % _nrOfRequest; + } + + return NULL; +} + +/** + * True if no communication is necessary, false if there is active communication + */ +bool FastCommunication::empty() { + // check if one fifo would like to send something to another SPE + for (int i = 0; i < _nrOfQueues; i++) { + if (_fifos[i].type == this->out) { + if (_fifos[i].iswfifo) { + if (_fifos[i].wfifo->used() > 0) + { + return false; + } + } else { + if (_fifos[i].fifo->used() > 0) + { + return false; + } + } + } + } + + // are open sendings? + for (int i = 0; i < _nrOfRequest; i++) { + if (_request[i].valid) { + return false; + } + } + + return true; +} + +void FastCommunication::sendMessage(uint32_t message, int32_t process) { + // Simple forward the message to the PPE + if (process <= -1) { + spu_write_out_mbox(message); + } + + // Forward message to the corresponding SPE + else { + uint32_t tag_id; + + // reserve DMA tag ID + if ((tag_id = mfc_tag_reserve()) == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return; + } + + //printf("SPE > OUT Message to process = %d, addr = %llx\n", process, _ea_base_all[process]); + write_in_mbox(message, _ea_base_all[process], tag_id); + mfc_tag_release(tag_id); + } +} + +int FastCommunication::testMessage(int32_t process) { + //int32_t process = queueOnSPE[queue]; + + // Simple forward the message to the PPE + if (process <= -1) { + return spu_stat_out_mbox(); + } + + // Forward message to the corresponding SPE + else { + uint32_t tag_id; + + // reserve DMA tag ID + if ((tag_id = mfc_tag_reserve()) == MFC_TAG_INVALID) { + printf("SPE: ERROR - can't reserve a tag ID\n"); + return 0; + } + + // IMPORTANT: THIS IS NOT RACE CONDITION FREE!!!! + int test = status_in_mbox(_ea_base_all[process], tag_id); + mfc_tag_release(tag_id); + return test; + } +} diff --git a/dol/src/dol/visitor/cell/lib/spu/FastCommunication.h b/dol/src/dol/visitor/cell/lib/spu/FastCommunication.h new file mode 100644 index 0000000..a787556 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/FastCommunication.h @@ -0,0 +1,119 @@ +/* + * FastCommunication.h + * + * Created on: Mar 3, 2009 + * Author: lschor + */ + +#ifndef FASTCOMMUNICATION_H_ +#define FASTCOMMUNICATION_H_ + +// CBE includes +#include +#include + +// C++ includes +#include +#include +#include +#include +#include +#include + +// Local includes +#include "Fifo.h" +#include "WindowedFifo.h" +#include "../common.h" + +// For external Mailbox Communication +#include "../spu_mfcio_ext.h" + +// Include to allocate/free using for DMA transfers +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +//Cell Macros +#define waittag(tag_id) mfc_write_tag_mask(1< _size) { + return _size - _tail; + } else { + return _pos; + } +} + +/* + * Is allowed to start a dma request + */ +bool Fifo::dmaAllowed() { + if (_blocked > 0) { + _blocked--; + return false; + } else { + return !_activeDMA; + } +} + +/** + * Test the implementation + */ +/* + int main() { + std::cout.width(5); + Fifo *myFifo = new Fifo(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 6; i++) { + std::cout << "write " << i << " to Fifo. "; + int write = myFifo->write(&i, sizeof(int)); + printf(" %d ", write); + if (write == sizeof(int)) { + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } else { + std::cout << std::endl; + } + } + for (int i = 0; i < 6; i++) { + int value; + int read = myFifo->read(&value, sizeof(int)); + printf(" %d ", read); + if (read == sizeof(int)) { + std::cout << "read " << value << " from Fifo "; + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } + } + } + delete myFifo; + return 0; + } + */ diff --git a/dol/src/dol/visitor/cell/lib/spu/Fifo.h b/dol/src/dol/visitor/cell/lib/spu/Fifo.h new file mode 100644 index 0000000..798e7dd --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/Fifo.h @@ -0,0 +1,47 @@ +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include +#include + +#include "../constant.h" + +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +class Fifo { + public: + Fifo(unsigned size); + virtual ~Fifo(); + + // Read / Write + virtual unsigned read(void *destination, unsigned len); + virtual unsigned write(const void *source, unsigned len); + + // Buffer functions + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + + // DMA functions + virtual char *getQueuePointer(); + virtual void dmaRead(unsigned len); + virtual unsigned dmaStart(); + virtual bool dmaAllowed(); + + // Global Variables + unsigned _inTail; + + protected: + char *_buffer; // Buffer pointer + + unsigned _tail; // Pointer to the tail + + unsigned _pos; // Amount used + unsigned _size; // Size of the buffer + + unsigned _blocked; // Number of blocking necessary + bool _activeDMA; // Active DMA? +}; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.cpp b/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.cpp new file mode 100644 index 0000000..9b03f6a --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.cpp @@ -0,0 +1,277 @@ +#include "WindowedFifo.h" + +/** + * + */ +WindowedFifo::WindowedFifo(unsigned size = 20) { + //std::cout << "Create WindowedFifo." << std::endl; + + _size = size; + _buffer = (char *) _malloc_align(_size, ALIGNMENT_FACTOR); + if(!_buffer) { + fprintf(stderr,"[WFIFO] Memory allocation failure\n"); + exit(-1); + } + _head = 0; + _tail = 0; + _headRoom = 0; + _tailRoom = 0; + _use = 0; + + _isHeadReserved = false; + _isTailReserved = false; + + // For DMA transfers + _blocked = 0; + _activeDMA = false; +} + +/** + * + */ +WindowedFifo::~WindowedFifo() { + //std::cout << "Delete WindowedFifo." << std::endl; + if (_buffer) { + _free_align(_buffer); + } + _buffer = 0; + _head = 0; + _tail = 0; + _use = 0; + //std::cout << "Deleted WindowedFifo." << std::endl; +} + +/** + * + */ +unsigned WindowedFifo::reserve(char** destination, unsigned len) { + + //std::cout << "Attempt to reserve " << len << " bytes." << std::endl; + + //can only reserve once piece at a time + if (_isHeadReserved) { + *destination = 0; + return 0; + } + + //reserve at most as much memory as still available in the buffer + unsigned write = (len <= _size - _use ? len : 0); + + if (write > 0) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_head + write > _size) { + write = _size - _head; + } + + _headRoom = (_head + write) == _size ? 0 : _head + write; + *destination = &(_buffer[_head]); + _isHeadReserved = true; + } + + //std::cout << "Reserved " << write << " bytes." << std::endl; + _writeReserve = write; + return write; +} + +/** + * + */ +void WindowedFifo::release() { + if (_isHeadReserved) { + //std::cout << "Released " << _headRoom - _head << " bytes." << std::endl; + _head = _headRoom; + _use += _writeReserve; + _isHeadReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::capture(char **destination, unsigned len) { + + //std::cout << "Attempt to capture " << len << " bytes." << std::endl; + + if (_isTailReserved) { + //std::cout << "Only one attempt to capture allowed." << std::endl; + *destination = 0; + return 0; + } + + //capture at most as much data as available in the buffer + unsigned read = (len <= _use ? len : 0); + + if (read > 0) { + //if wrap-around in buffer: return only buffer for the + //conntiguous buffer space + if (_tail + read > _size) { + read = _size - _tail; + } + + _tailRoom = (_tail + read) == _size ? 0 : _tailRoom = _tail + read; + *destination = &(_buffer[_tail]); + _isTailReserved = true; + } + + _readReserve = read; + //std::cout << "Captured " << read << " bytes." << std::endl; + + return read; +} + +/** + * + */ +void WindowedFifo::consume() { + if (_isTailReserved) { + //std::cout << "Consumed " << _tailRoom - _tail << " bytes." << std::endl; + _tail = _tailRoom; + _use -= _readReserve; + _isTailReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::size() const { + return _size; +} + +/** + * + */ +unsigned WindowedFifo::unused() const { + return _size - _use; +} + +/** + * + */ +unsigned WindowedFifo::used() const { + return _use; +} + +/* + * Get the pointer to the start of the queue + */ +char *WindowedFifo::getQueuePointer() { + return _buffer; +} + +/* + * Has completed a dma read process, i.e. has read out of the queue + */ +void WindowedFifo::dmaRead(unsigned len) { + if (len == 0) { + _blocked = BLOCKED_MAX_NR; + } else { + _tail = ((unsigned) (_tail + len) % _size); + _use -= len; + } + + _activeDMA = false; +} + +/* + * Start a DMA request, returns the current space one have + */ +unsigned WindowedFifo::dmaStart() { + _activeDMA = true; + + if (_tail + _use > _size) { + return _size - _tail; + } else { + return _use; + } +} + +/* + * Is allowed to start a dma request + */ +bool WindowedFifo::dmaAllowed() { +#ifndef STORE_REQUESTS + if (_blocked > 0) { + _blocked--; + return false; + } else { + return !_activeDMA; + } +#else + return !_activeDMA; +#endif +} + +/** + * Is needed for DMA transfers + */ +unsigned WindowedFifo::dmaWrite(const void *source, unsigned len) { + + char* buffer = (char*) source; + + if (_head + len < _size) { + memcpy(_buffer + _head, buffer, len); + } else { + // We should never be here! + memcpy(_buffer + _head, buffer, _size - _head); + memcpy(_buffer, buffer + _size - _head, len - _size + _head); + } + _use += len; + _head = (_head + len) >= _size ? _head + len - _size : _head + len; + + return len; +} + +/** + * Test the implementation + */ +/* + int main() { + WindowedFifo *myFifo = new WindowedFifo(16); + + int* buf1; + int* buf2; + int x = myFifo->reserve((char**)&buf1, 8); + *buf1 = 10; + *(buf1 + 1) = 20; + myFifo->release(); + int y = myFifo->capture((char**)&buf2, 8); + std::cout << "read " << *buf2 << " " << *(buf2 + 1) << std::endl; + myFifo->consume(); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 6; i++) { + std::cout << "write " << i << " to Fifo. "; + int write = myFifo->reserve((char**)&buf1, sizeof(int)); + if (write == sizeof(int)) { + *buf1 = i; + myFifo->release(); + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } else { + std::cout << std::endl; + } + } + for (int i = 0; i < 16; i++) { + char* buf3; + int read = myFifo->capture((char**)&buf3, sizeof(char)); + if (read == sizeof(char)) { + std::cout << "read " << (unsigned)*buf3 << " from Fifo "; + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + myFifo->consume(); + } else { + std::cout << "read nothing from Fifo." << std::endl; + } + + } + } + delete myFifo; + return 0; + } + */ diff --git a/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.h b/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.h new file mode 100644 index 0000000..5ef97d9 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/WindowedFifo.h @@ -0,0 +1,60 @@ +#ifndef _WINDOWEDFIFO_H_ +#define _WINDOWEDFIFO_H_ + +#include +#include + +#include "../constant.h" + +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +class WindowedFifo { + public: + WindowedFifo(unsigned size); + virtual ~WindowedFifo(); + + // Write + virtual unsigned reserve(char** destination, unsigned len); + virtual void release(); + + // Read + virtual unsigned capture(char** destination, unsigned len); + virtual void consume(); + + // General functions + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + + // DMA functions + virtual char *getQueuePointer(); + virtual void dmaRead(unsigned len); + virtual unsigned dmaStart(); + virtual bool dmaAllowed(); + virtual unsigned dmaWrite(const void *source, unsigned len); + + // Global variables + unsigned _inTail; + + protected: + char *_buffer; // Pointer to the buffer + + unsigned _head; // Current position of the head + unsigned _tail; // Current position of the tail + unsigned _headRoom; // HeadRoom pointer + unsigned _tailRoom; // Tailroom pointer + + unsigned _size; // Total space of the buffer + unsigned _use; // How many data are used in the buffer + unsigned _writeReserve; // Number of tokens one is writing + unsigned _readReserve; // Number of tokens one is reading + + bool _isHeadReserved; // Head reserved? + bool _isTailReserved; // Tail reserved? + + unsigned _blocked; // Blocked number the request has to wait + bool _activeDMA; // Is there an active DMA on this buffer +}; + +#endif diff --git a/dol/src/dol/visitor/cell/lib/spu/common.cpp b/dol/src/dol/visitor/cell/lib/spu/common.cpp new file mode 100644 index 0000000..df56fc8 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/common.cpp @@ -0,0 +1,29 @@ +/* + * common.cpp + * + * Created on: Feb 27, 2009 + * Author: lschor + */ + +#include "../common.h" + +/** + Round a number to the alignment of the Cell + */ +uint32_t roundDMA(uint32_t number) { + if (number > 16) + if (number % 16 == 0) + return number; + else + return number + 16 - (number % 16); + else if (number > 8) + return 16; + else if (number > 4) + return 8; + else if (number > 2) + return 4; + else if (number > 1) + return 2; + else + return 1; +} diff --git a/dol/src/dol/visitor/cell/lib/spu/dolSupport.cpp b/dol/src/dol/visitor/cell/lib/spu/dolSupport.cpp new file mode 100644 index 0000000..4bf2b6e --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/dolSupport.cpp @@ -0,0 +1,98 @@ +#include "dolSupport.h" + +/** + * + */ +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p) { + unsigned int + pos = + static_cast ((static_cast (p->wptr))->wrapper)->readPos; + pos += ((Fifo*) fifo)->read((char *) buf + pos, len - pos); + + if (pos == len) + static_cast ((static_cast (p->wptr))->wrapper)->readPos + = 0; + else + static_cast ((static_cast (p->wptr))->wrapper)->readPos + = pos; + return pos; +} + +/** + * + */ +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p) { + unsigned int + pos = + static_cast ((static_cast (p->wptr))->wrapper)->writePos; + pos += ((Fifo*) fifo)->write((char *) buf + pos, len - pos); + + if (pos == len) + static_cast ((static_cast (p->wptr))->wrapper)->writePos + = 0; + else + static_cast ((static_cast (p->wptr))->wrapper)->writePos + = pos; + return pos; +} + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast ((static_cast (p->wptr))->wrapper)->detach(); +} + +/** + * + */ +unsigned reserve(void* fifo, void** destination, unsigned len, + DOLProcess* p) { + return ((WindowedFifo*) fifo)->reserve((char **) destination, len); +} + +/** + * + */ +void release(void* fifo, DOLProcess* p) { + ((WindowedFifo*) fifo)->release(); +} + +/** + * + */ +unsigned capture(void* fifo, void** destination, unsigned len, + DOLProcess* p) { + return ((WindowedFifo*) fifo)->capture((char **) destination, len); +} + +/** + * + */ +void consume(void* fifo, DOLProcess* p) { + ((WindowedFifo*) fifo)->consume(); +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0) { + *port = (void**) ((void**) base)[index0]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1) { + *port = (void**) ((void**) base)[index0 * range1 + index1]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1, int index2, + int range2) { + *port = (void**) ((void**) base)[index0 * range1 * range2 + index1 + * range2 + index2]; +} + +void createPort(void** port, void* base, int number_of_indices, + int index0, int range0, int index1, int range1, int index2, + int range2, int index3, int range3) { + *port = (void**) ((void**) base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + index2 * range3 + index3]; +} diff --git a/dol/src/dol/visitor/cell/lib/spu/dolSupport.h b/dol/src/dol/visitor/cell/lib/spu/dolSupport.h new file mode 100644 index 0000000..63f1f74 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/dolSupport.h @@ -0,0 +1,88 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include + +#include "dol.h" +#include "../pt/pt.h" +#include "proc_wrapper.h" + +#include "Fifo.h" +#include "WindowedFifo.h" + + +typedef struct _process_data { + int lc; + proc_wrapper *wrapper; +} process_data; + + +#define DOL_read(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), read(port, buf, size, process) == size); + +#define DOL_write(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), write(port, buf, size, process) == size); + +#define DOL_reserve(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), reserve(port, (void**)buf, size, process) == size); + + //should stall only if the return size is 0!!! (but how, one has to give a return value...) + +#define DOL_release(port, process) \ + release(port, process); + +#define DOL_capture(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), capture(port, (void**)buf, size, process) == size); + + //should stall only if the return size is 0!!! (but how, one has to give a return value...) + +#define DOL_consume(port, process) \ + consume(port, process); + + +void DOL_detach(DOLProcess* p); + +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p); + +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p); + +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p); + +void release(void* fifo, DOLProcess* p); + +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p); + +void consume(void* fifo, DOLProcess* p); + + +//macros to deal with iterated ports +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) static Fifo *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#define GETINDEX(dimension) \ + static_cast((static_cast(p->wptr))->wrapper)->getIndex(dimension) + +void createPort(void** port, void* base, int number_of_indices, int index0, int range0); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2, int index3, int range3); + +#endif diff --git a/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.cpp b/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.cpp new file mode 100644 index 0000000..41e2705 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.cpp @@ -0,0 +1,92 @@ +/** + * proc_wrapper.cpp + * + * Created on: Feb 24, 2009 + * Author: lschor + */ + +#include "proc_wrapper.h" + +proc_wrapper::proc_wrapper() { + _isDetached = false; + readPos = 0; + writePos = 0; +} + +proc_wrapper::~proc_wrapper() { +} + +/** + * + */ +void proc_wrapper::init() { + _process.init(&_process); +} + +/** + * + */ +int proc_wrapper::fire() { + return _process.fire(&_process); +} + +/** + * + */ +void proc_wrapper::detach() { + _isDetached = true; +} + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int proc_wrapper::getIndex(const char* string, char* tokens, + int indexNumber) { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + free(string_copy); + return -1; +} + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int proc_wrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} diff --git a/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.h b/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.h new file mode 100644 index 0000000..656fcdb --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu/proc_wrapper.h @@ -0,0 +1,55 @@ +/* + * proc_wrapper.h + * + * Created on: Feb 24, 2009 + * Author: lschor + */ + +#ifndef PROC_WRAPPER_H_ +#define PROC_WRAPPER_H_ + +#include +#include + +#include +#include +#include + +//CBE Macros +#define waittag(tag_id) mfc_write_tag_mask(1< current position you are while blocking + unsigned int readPos; + unsigned int writePos; + +protected: + char* name; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; + + uint32_t* port_id; + uint32_t* port_queue_id; + uint32_t number_of_ports; + virtual int getIndex(const char* string, char* tokens, + int indexNumber); +}; + +#endif /* PROC_WRAPPER_H_ */ diff --git a/dol/src/dol/visitor/cell/lib/spu_mfcio_ext.h b/dol/src/dol/visitor/cell/lib/spu_mfcio_ext.h new file mode 100644 index 0000000..d85f1e8 --- /dev/null +++ b/dol/src/dol/visitor/cell/lib/spu_mfcio_ext.h @@ -0,0 +1,173 @@ +// -------------------------------------------------------------- +// (C)Copyright 2007, +// International Business Machines Corporation, +// All Rights Reserved. +// -------------------------------------------------------------- + +#ifndef _spu_mfcio_ext_h_ +#define _spu_mfcio_ext_h_ + +#include +#include +#include + +#include +#include + +static uint32_t msg[4]__attribute__ ((aligned (16))); + +// ========================================================================== +// Definitions +// ========================================================================== +#define SPU_IN_MBOX_OFFSET 0x0C // offset of mailbox status register from control area base +#define SPU_IN_MBOX_OFFSET_SLOT 0x3 // 16B alignment of mailbox status register = (SPU_MBOX_STAT_OFFSET&0xF)>>2 +#define SPU_MBOX_STAT_OFFSET 0x14 // offset of mailbox status register from control area base +#define SPU_MBOX_STAT_OFFSET_SLOT 0x1 // 16B alignment of mailbox status register = (SPU_MBOX_STAT_OFFSET&0xF)>>2 + +#define SPU_SIG_NOTIFY_OFFSET 0x0C // offset of signal notify 1 or 2 registera from signal notify 1 or 2 areas base +#define SPU_SIG_NOTIFY_OFFSET_SLOT 0x3 // 16B alignment of signal notify 1 or 2 register = (SPU_SIG_NOTIFY_OFFSET&0xF)>>2 + +// ========================================================================== +// Functions definitions +// ========================================================================== + +inline int status_mbox(uint64_t ea_mfc, uint32_t tag_id); +inline int status_in_mbox(uint64_t ea_mfc, uint32_t tag_id); +inline int status_out_mbox(uint64_t ea_mfc, uint32_t tag_id); +inline int status_outintr_mbox(uint64_t ea_mfc, uint32_t tag_id); + +int write_in_mbox(uint32_t data, uint64_t ea_mfc, uint32_t tag_id); +int write_signal1(uint32_t data, uint64_t ea_mfc, uint32_t tag_id); +int write_signal2(uint32_t data, uint64_t ea_mfc, uint32_t tag_id); + +// returns the value of mailbox status register of remote SPE +inline int status_mbox(uint64_t ea_mfc, uint32_t tag_id) +{ + uint32_t status[4], idx; + uint64_t ea_stat_mbox = ea_mfc + SPU_MBOX_STAT_OFFSET; + + //printf(">16, (status[idx]&0x0000ff00)>>8, (status[idx]&0x000000ff) ); + //printf(">8; + + //printf(">16; + + //printf(" +#include + +#include +#include +#include +#include + +// Include to allocate/free using for DMA transfers +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" + +// Local includes +#include "../lib/spu/Fifo.h" +#include "../lib/common.h" +//#include "../lib/spu/Communication.h" +#include "../lib/spu/FastCommunication.h" + +// Protothread includes +#include "../lib/pt/pt.h" + +// Context file +static spu_context ctx_spu __attribute__ ((aligned (128))); + +#endif /* SPU_OS_H_ */ diff --git a/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.cpp b/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.cpp new file mode 100644 index 0000000..ebb1f4c --- /dev/null +++ b/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.cpp @@ -0,0 +1,66 @@ +/* + * Square_wrapper.cpp + * + * Created on: Feb 27, 2009 + * Author: lschor + */ + + +#include "@PROCESSNAME@Wrapper.h" +#include "../lib/spu/dolSupport.h" + +#include "@PROCESSNAME@.c" + +@PROCESSNAME@Wrapper::@PROCESSNAME@Wrapper(uint64_t argp) { + // reserve DMA tag ID + uint32_t tag_id; + if((tag_id=mfc_tag_reserve())==MFC_TAG_INVALID){ + printf("SPE: ERROR - can't reserve a tag ID\n"); return; + } + + // Get the context information for the whole system + mfc_get((void*) &ctx_proc, argp, sizeof(ctx_proc), tag_id, 0, 0); + mfc_write_tag_mask(1<name = (char *)_malloc_align((uint32_t)ctx_proc.processNameLen, 4); + if(!this->name) { + fprintf(stderr,"[SPUProcessWrapper] Memory allocation failure\n"); + exit(-1); + } + mfc_get((void *)this->name, ctx_proc.processName, ctx_proc.processNameLen, tag_id, 0, 0); + waittag(tag_id); + + + //initialize the index array + for (int i = 0; i < 4; i++) { + this->_iteratorIndex[i] = getIndex(this->name, "_", i); + } + + try { _state = (LocalState) new @PROCESSNAME_UPPER@_State; } + catch(std::bad_alloc &e) { + fprintf(stderr, "[SPUProcessWrapper] Memory allocation failure\n"); + exit(-1); + } + _process.local = _state; + _process.init = @PROCESSNAME@_init; + _process.fire = @PROCESSNAME@_fire; + _process.wptr = (void*)&_wrapper_data; + + _wrapper_data.wrapper = this; + + // Init the process + _process.init(&_process); + + // release tag ID before exiting + mfc_tag_release(tag_id); +} + +@PROCESSNAME@Wrapper::~@PROCESSNAME@Wrapper() { + // Free the state + if (_state) + delete (@PROCESSNAME_UPPER@_State*) _state; + + // Free the name of the wrapper + _free_align(this->name); +} diff --git a/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.h b/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.h new file mode 100644 index 0000000..a0f9800 --- /dev/null +++ b/dol/src/dol/visitor/cell/template/spu_process_wrapper_template.h @@ -0,0 +1,56 @@ +/**************************************************************** + * Process Wrapper file + * Creator: lschor, 2009-02-24 + * Description: Wrapper for a specific SPE process + * + * Revision: + * - 2009-02-24: Created + */ + +#ifndef @PROCESSNAME@_WRAPPER_H_ +#define @PROCESSNAME@_WRAPPER_H_ + +// General includes +#include +#include +#include +#include +#include +#include + +// Include to allocate/free using for DMA transfers +#include "../lib/malloc_align.h" +#include "../lib/free_align.h" +#include "../lib/spu/Fifo.h" +#include "../lib/spu/WindowedFifo.h" + +// Local includes +#include "../lib/common.h" +//#include "../lib/estimation.h" + +#include "../lib/spu/proc_wrapper.h" + +class @PROCESSNAME@Wrapper; + +typedef struct _@PROCESSNAME@_data { + int lc; + @PROCESSNAME@Wrapper *wrapper; +} @PROCESSNAME@_data; + +class @PROCESSNAME@Wrapper : public proc_wrapper { + +// Context file +public: + @PROCESSNAME@Wrapper(uint64_t argp); + virtual ~@PROCESSNAME@Wrapper(); + + @FIFO@ + +protected: + LocalState _state; + process_context ctx_proc __attribute__ ((aligned (128))); + @PROCESSNAME@_data _wrapper_data; + +}; + +#endif /* @PROCESSNAME@_WRAPPER_H_ */ diff --git a/dol/src/dol/visitor/dot/ArchDotVisitor.java b/dol/src/dol/visitor/dot/ArchDotVisitor.java new file mode 100644 index 0000000..5f71ee8 --- /dev/null +++ b/dol/src/dol/visitor/dot/ArchDotVisitor.java @@ -0,0 +1,250 @@ +/* $Id: ArchDotVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.dot; + +import java.util.Iterator; + +import dol.datamodel.architecture.ArchiConnection; +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.HWChannel; +import dol.datamodel.architecture.Memory; +import dol.datamodel.architecture.Processor; +import dol.datamodel.architecture.ReadPath; +import dol.datamodel.architecture.WritePath; +import dol.datamodel.pn.Process; +import dol.util.CodePrintStream; +import dol.visitor.ArchiVisitor; + +/** + * Helps to generate DOTTY information for architecture resources. + */ +public class ArchDotVisitor extends ArchiVisitor +{ + /** + * Constructor. + * + * @param printStream print stream to which the contents is written + */ + public ArchDotVisitor(CodePrintStream printStream) { + _printStream = printStream; + _pnVisitor = new PNDotVisitor(printStream); + } + + /** + * Print a .dot file in the correct format for DOTTY. + * + * @param arch architecture that needs to be rendered + */ + public void visitComponent(Architecture arch) { + _printStream.printPrefixln("digraph architecture {"); + _printStream.println(); + _printStream.prefixInc(); + _printStream.printPrefixln("ratio = auto;"); + _printStream.printPrefixln("rankdir = LR;"); + _printStream.printPrefixln("ranksep = 2;"); + _printStream.printPrefixln("nodesep = 0.2;"); + _printStream.printPrefixln("center = true;"); + _printStream.printPrefixln(""); + _printStream.printPrefixln("node [ fontsize=12, height=0.4, " + + "width=0.4, style=filled, color=\"0.65 0.20 1.00\" ]"); + _printStream.println(); + + //visit all processors + Processor processor; + Iterator processorIter = arch.getProcessorList().iterator(); + while( processorIter.hasNext() ) { + processor = processorIter.next(); + processor.accept(this); + } + + //visit all hw_channels + HWChannel chan; + Iterator chanIter = arch.getHWChannelList().iterator(); + while(chanIter.hasNext()) { + chan = chanIter.next(); + chan.accept(this); + } + + //visit all memories + Memory mem; + Iterator memIter = arch.getMemoryList().iterator(); + while(memIter.hasNext()) { + mem = memIter.next(); + mem.accept(this); + } + + //visit all connections + ArchiConnection cn; + Iterator cnIter = arch.getConnectionList().iterator(); + while(cnIter.hasNext()) { + cn = cnIter.next(); + cn.accept(this); + } + + ReadPath rPath; + Iterator rpIter = arch.getReadPathList().iterator(); + while(rpIter.hasNext()) { + rPath = rpIter.next(); + rPath.accept(this); + } + + WritePath wPath; + Iterator wpIter = arch.getWritePathList().iterator(); + while(wpIter.hasNext()) { + wPath = wpIter.next(); + wPath.accept(this); + } + + _printStream.prefixDec(); + _printStream.println(); + _printStream.printPrefixln("}"); + } + + public void visitComponent(HWChannel chan) + { + _printStream.printPrefixln("subgraph cluster_" + + chan.getName().replaceAll("\\.", "") + " {"); + _printStream.prefixInc(); + _printStream.printPrefixln("label = \"" + chan.getName() + "\""); + + if (!chan.getPathList().isEmpty()) { + Iterator pIter = chan.getPathList().iterator(); + String path; + while (pIter.hasNext()) { + path = (String) pIter.next(); + _printStream.printPrefixln("\"" + path + "_" + chan.getName() + + "\" [label=\" " + + "\" shape=circle style=solid]"); + } + } + + _printStream.prefixDec(); + _printStream.printPrefixln("}"); + _printStream.println(); + } + + public void visitComponent(Memory mem) + { + _printStream.printPrefixln("subgraph cluster_" + + mem.getName().replaceAll("\\.", "") + " {"); + _printStream.prefixInc(); + _printStream.printPrefixln("label = \"" + mem.getName() + "\""); + + if (!mem.getRXBufList().isEmpty()) { + Iterator rIter = mem.getRXBufList().iterator(); + String rxBuf; + while (rIter.hasNext()) { + rxBuf = (String) rIter.next(); + _printStream.printPrefixln("\"" + rxBuf.replaceAll("\\.", "") + + "_RX\" [label=\"RX" + + "\" shape=circle style=dotted]"); + } + } + + if (!mem.getTXBufList().isEmpty()) { + Iterator rIter = mem.getTXBufList().iterator(); + String txBuf; + while (rIter.hasNext()) { + txBuf = (String) rIter.next(); + _printStream.printPrefixln("\"" + txBuf.replaceAll("\\.", "") + + "_TX\" [label=\"TX" + + "\" shape=circle style=dashed]"); + } + } + + if (!mem.getCHBufList().isEmpty()) { + Iterator rIter = mem.getCHBufList().iterator(); + String chBuf; + while (rIter.hasNext()) { + chBuf = (String) rIter.next(); + _printStream.printPrefixln("\"" + chBuf.replaceAll("\\.", "") + + "_CH\" [label=\"CH" + + "\" shape=circle style=bold]"); + } + } + + _printStream.prefixDec(); + _printStream.printPrefixln("}"); + _printStream.println(); + } + + + public void visitComponent(ArchiConnection cn) + { + _printStream.printPrefix(); + _printStream.print("\"" + + cn.getOrigin().getName().replaceAll("\\.", "") + "\" -> \"" + + cn.getTarget().getName().replaceAll("\\.", "") + + "\" [ color=" + _color + " ];"); + _printStream.println(); + } + + public void visitComponent(ReadPath rp) + { + String rName = rp.getName().replaceAll("\\.", ""); + + _printStream.printPrefix(); + _printStream.print(rName + "_RPath_CH->" ); + + Iterator cIter = rp.getHWChannelList().iterator(); + while (cIter.hasNext()) { + HWChannel channel = cIter.next(); + _printStream.print(rName + "_RPath_" + + channel.getName().replaceAll("\\.", "") + + "->"); + } + _printStream.println(rName + "_RX"); + } + + public void visitComponent(WritePath rp) + { + String rName = rp.getName().replaceAll("\\.", ""); + + _printStream.printPrefix(); + _printStream.print(rName + "_TX->" ); + + Iterator cIter = rp.getHWChannelList().iterator(); + while (cIter.hasNext()) { + HWChannel channel = cIter.next(); + _printStream.print(rName + "_WPath_" + + channel.getName().replaceAll("\\.", "") + + "->"); + } + _printStream.println(rName + "_WPath_CH"); + } + + /** + * Clusters all processes of this processor. + */ + public void visitComponent(Processor processor) + { + /* + _printStream.printPrefix(); + _printStream.print("\"" + processor.getName() + "\" [ label=\"" + + processor.getName() + "\", color=" + _color + + ", shape=box];"); + _printStream.println(); + */ + + _printStream.printPrefixln("subgraph cluster_" + + processor.getName().replaceAll("\\.", "") + " {"); + _printStream.prefixInc(); + _printStream.printPrefixln("label = \"" + processor.getName() + "\""); + + // labeleling clusters with dot2.8 & graphviz2.8 seems buggy + //_printStream.printPrefixln("label = \"" + processor.getName()+"\";"); + if (!processor.getProcessList().isEmpty()) { + Iterator pIter = processor.getProcessList().iterator(); + while (pIter.hasNext()) { + pIter.next().accept(_pnVisitor); + } + } + _printStream.prefixDec(); + _printStream.printPrefixln("}"); + _printStream.println(); + } + + /** DOT Visitor to visit process network resources */ + protected PNDotVisitor _pnVisitor = null; + + protected String _color = "dimgray"; +} diff --git a/dol/src/dol/visitor/dot/MapDotVisitor.java b/dol/src/dol/visitor/dot/MapDotVisitor.java new file mode 100644 index 0000000..581c615 --- /dev/null +++ b/dol/src/dol/visitor/dot/MapDotVisitor.java @@ -0,0 +1,80 @@ +/* $Id: MapDotVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.dot; + +import java.util.Iterator; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Channel; +import dol.util.CodePrintStream; +import dol.visitor.MapVisitor; + +/** + * This class is a class for a visitor that is used to generate + * ".dot" output in order to visualize a mapping using the DOTTY tool. + */ +public class MapDotVisitor extends MapVisitor { + + /** + * Constructor. + * + * @param printStream print stream to which the contents is written + */ + public MapDotVisitor(CodePrintStream printStream) { + _printStream = printStream; + _pnVisitor = new PNDotVisitor(printStream); + _archiVisitor = new ArchDotVisitor(printStream); + } + + /** + * Print a .dot file in the correct format for DOTTY. + * + * @param map process network that needs to be rendered + */ + public void visitComponent(Mapping map) { + _printStream.printPrefixln("digraph mapping {"); + _printStream.println(); + _printStream.prefixInc(); + _printStream.printPrefixln("ratio = auto;"); + _printStream.printPrefixln("rankdir = LR;"); + _printStream.printPrefixln("ranksep = 0.3;"); + _printStream.printPrefixln("nodesep = 0.2;"); + _printStream.printPrefixln("center = true;"); + _printStream.printPrefixln(""); + _printStream.printPrefixln("node [ fontsize=12, height=0.4, " + + "width=0.4, style=filled, color=\"0.65 0.20 1.00\" ];"); + _printStream.printPrefixln("edge [ fontsize=10, arrowhead=normal, " + + "arrowsize=0.8, style=\"setlinewidth(2)\" ];"); + _printStream.println(); + + //visit all processors + Processor processor; + Iterator processorIter = map.getProcessorList().iterator(); + while( processorIter.hasNext() ) + { + processor = processorIter.next(); + processor.accept(_archiVisitor); + } + + //visit all channels (from PN) + _pnVisitor.setMapping(map); + Channel chan; + Iterator chanIter = map.getPN().getChannelList().iterator(); + while (chanIter.hasNext() ) + { + chan = chanIter.next(); + chan.accept(_pnVisitor); + } + + _printStream.prefixDec(); + _printStream.println(); + _printStream.printPrefixln("}"); + } + + + /** DOT Visitor to visit process network resources */ + protected PNDotVisitor _pnVisitor = null; + + /** DOT Visitor to visit architecture resources */ + protected ArchDotVisitor _archiVisitor = null; +} diff --git a/dol/src/dol/visitor/dot/PNDotVisitor.java b/dol/src/dol/visitor/dot/PNDotVisitor.java new file mode 100644 index 0000000..3a0205a --- /dev/null +++ b/dol/src/dol/visitor/dot/PNDotVisitor.java @@ -0,0 +1,163 @@ +/* $Id: PNDotVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.dot; + +import java.util.Iterator; + +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * ".dot" output in order to visualize a PN using the DOTTY tool. + */ +public class PNDotVisitor extends PNVisitor { + + Mapping _mapping = null; + + /** + * Constructor. + * + * @param printStream print stream to which the contents is written + */ + public PNDotVisitor(CodePrintStream printStream) { + _printStream = printStream; + } + + /** + * Print a .dot file in the correct format for DOTTY. + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + _printStream.printPrefixln("digraph pn {"); + _printStream.println(); + _printStream.prefixInc(); + _printStream.printPrefixln("ratio = auto;"); + _printStream.printPrefixln("rankdir = LR;"); + _printStream.printPrefixln("ranksep = 0.3;"); + _printStream.printPrefixln("nodesep = 0.2;"); + _printStream.printPrefixln("center = true;"); + _printStream.printPrefixln(""); + _printStream.printPrefixln("node [ fontsize=12, height=0.4, " + + "width=0.4, style=filled, color=\"0.65 0.20 1.00\" ];"); + _printStream.printPrefixln("edge [ fontsize=10, arrowhead=normal, " + + "arrowsize=0.8, style=\"setlinewidth(2)\" ];"); + _printStream.println(); + + //visit all processes + Iterator pIter; + Process p; + pIter = x.getProcessList().iterator(); + while (pIter.hasNext()) + { + p = pIter.next(); + p.accept(this); + } + _printStream.println(); + + //visit all channels + Iterator cIter; + Channel c; + cIter = x.getChannelList().iterator(); + while( cIter.hasNext() ) { + c = cIter.next(); + c.accept(this); + } + + _printStream.prefixDec(); + _printStream.println(); + _printStream.printPrefixln("}"); + } + + /** + * Print a line for the process in the correct format for DOTTY. + * + * @param x process that needs to be rendered + */ + public void visitComponent(Process x) { + //other colors: beige, lightgoldenrod, orange, tan, khaki3, + //aliceblue, lightskyblue, lightseagreen, mintcream, + //burlywood3, lightblue1, linen, papayawhip, azure1,2,3 + String color = "lightskyblue"; + + _printStream.printPrefix(); + _printStream.print("\"" + x.getName() + "\" [ label=\"" + + x.getName() + "\", color=" + + color); + if (!x.hasInPorts() || !x.hasOutPorts()) + _printStream.print(", shape=diamond"); + else + _printStream.print(", shape=ellipse"); + _printStream.print(" ];"); + _printStream.println(); + } + + /** + * Print a line for the channel in the correct format for DOTTY. + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + String color = "lightblue3"; + + //change the color of the channel to indicate whether connection + //is on-tile or off-tile + if (_mapping != null) { + String process1 = _mapping.getPN().getProcess(x.getOrigin(). + getName()).getName(); + String process2 = _mapping.getPN().getProcess(x.getTarget(). + getName()).getName(); + String processor1 = ""; + String processor2 = ""; + + for (ComputationBinding binding : + _mapping.getCompBindList()) { + if (binding.getProcess().getName(). + equals(process1)) { + processor1 = binding.getProcessor().getName(); + } else if (binding.getProcess().getName(). + equals(process2)) { + processor2 = binding.getProcessor().getName(); + } + } + if (processor1.equals(processor2)) { + } else if (processor1.length() >= 6 && processor2.length() >=6 + && processor1.substring(0, 6).equals( + processor2.substring(0, 6))) { + color = "orange"; + } else { + color = "red"; + } + } + + + Iterator i; + Port port, portNext; + i = x.getPortList().iterator(); + + port = (Port) i.next(); + + portNext = (Port) i.next(); + _printStream.printPrefix(); + + _printStream.print("\"" + port.getPeerResource().getName() + + "\" -> " + "\"" + portNext.getPeerResource().getName() + + "\" [" ); + _printStream.print(" label=\"" + x.getName() + "\"" + + ", color=" + color + " ];"); + _printStream.println(); + } + + /** + * + */ + public void setMapping(Mapping mapping) { + _mapping = mapping; + } +} diff --git a/dol/src/dol/visitor/dot/package.html b/dol/src/dol/visitor/dot/package.html new file mode 100644 index 0000000..f219d05 --- /dev/null +++ b/dol/src/dol/visitor/dot/package.html @@ -0,0 +1,21 @@ + + + + + + +Dotty graphical representation generation. +This package will generate a dotty representation for a given example. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/hds/HdsMakefileVisitor.java b/dol/src/dol/visitor/hds/HdsMakefileVisitor.java new file mode 100644 index 0000000..9256b2a --- /dev/null +++ b/dol/src/dol/visitor/hds/HdsMakefileVisitor.java @@ -0,0 +1,93 @@ +/* $Id: HdsMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.hds; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.Configuration; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a HdS package Makefile. + */ +public class HdsMakefileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public HdsMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param x process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("CXX = g++"); + ps.println("CC = g++"); + ps.println(); + ps.println("PREPROC_MACROS = -D__DOL_ETHZ_GEN__ " + + " -DINCLUDE_PROFILER #-DINCLUDE_PERFORMANCE" + + " #-DINCLUDE_TRACE"); + ps.println(); + ps.println("SYSTEMC_INC = -I" + _ui.getSystemCINC()); + ps.println("SYSTEMC_LIB = " + _ui.getSystemCLIB()); + ps.println("MY_LIB_INC = -Ilib -Isc_wrappers -Iprocesses"); + ps.println("VPATH = lib:sc_wrappers:processes"); + ps.println(); + ps.println("CXXFLAGS = -g -O0 -Wall $(PREPROC_MACROS) " + + "$(SYSTEMC_INC) $(MY_LIB_INC)"); + ps.println("CFLAGS = $(CXXFLAGS)"); + ps.println(); + + ps.print("PROCESS_OBJS = dolSupport.o ProcessWrapper.o " + + "Fifo.o WindowedFifo.o "); + + for (String basename : x.getProcessBasenames()) { + ps.print(basename + "_wrapper.o "); + } + + for (Configuration conf : x.getCfgList()) { + if (conf.getName().equals("EXTERNAL_SRC")) { + ps.print(conf.getValue() + " "); + } + } + + ps.println("#xmlParser.o Performance_Extraction.o " + + "functional_trace.o"); + ps.println(); + ps.println("all:" + _name); + ps.println(); + ps.println(_name + ": " + _name + ".o $(PROCESS_OBJS)"); + ps.print("\t$(CXX) $(CXXFLAGS) -o $@ $^ $(SYSTEMC_LIB) "); + for (Configuration conf : x.getCfgList()) { + if (conf.getName().equals("DYNAMIC_LINK")) + ps.print(conf.getValue() + " "); + } + ps.println("# -lpthread -lX11 -lrt"); + ps.println("clean:"); + ps.println("\t-rm -f *.o core core.* *.core *.tga " + + "static_characterization.xml " + _name); + + } catch (Exception e) { + System.out.println("HdsMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected String _name = "sc_application"; +} diff --git a/dol/src/dol/visitor/hds/HdsModuleVisitor.java b/dol/src/dol/visitor/hds/HdsModuleVisitor.java new file mode 100644 index 0000000..f00bbe3 --- /dev/null +++ b/dol/src/dol/visitor/hds/HdsModuleVisitor.java @@ -0,0 +1,347 @@ +/* $Id: HdsModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.hds; + +import java.io.FileOutputStream; +import java.io.OutputStream; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.Resource; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + */ +public class HdsModuleVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public HdsModuleVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "sc_application.cpp"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#include "); + + /* begin of profiling: standard i/o file handling routines */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("#include \"dol_sched_if.h\""); + _mainPS.println(); + + for (String basename : x.getProcessBasenames()) { + _mainPS.printPrefixln("#include \"" + basename + + "_wrapper.h\""); + } + _mainPS.println(); + _mainPS.printPrefixln("using namespace std;"); + + /* begin of profiling: global variables */ + _mainPS.println(); + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("#define PROFILER_OUTPUT_FILENAME \"profile.txt\""); + _mainPS.printPrefixln("FILE *profiler_output_file;"); + _mainPS.printPrefixln("unsigned int profiler_event_counter;"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.println(); + _mainPS.printPrefixln("class sc_application : public sc_module "); + _mainPS.printLeftBracket(); + + _mainPS.printPrefixln("public:"); + _mainPS.printPrefixln("SC_HAS_PROCESS(sc_application);"); + + //declare processes + _mainPS.println(); + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln(p.getBasename() + "_wrapper " + + p.getName() + "_ins"+ ";"); + _mainPS.printPrefixln("sc_event " + p.getName() + + "_event;"); + } + _mainPS.println(); + + //define the scheduler + _mainPS.printPrefixln("sc_event sched_event;"); + _mainPS.printPrefixln("list eventList;"); + _mainPS.printPrefixln("list::iterator iter;"); + _mainPS.println(); + + //declare channels + _mainPS.println(); + for (Channel p : x.getChannelList()) { + if (p.getType().equals("fifo")) { + _mainPS.printPrefixln("Fifo " + p.getName() + "_ins;"); + } else if (p.getType().equals("wfifo")) { + _mainPS.printPrefixln("WindowedFifo " + p.getName() + "_ins;"); + } + } + _mainPS.println(); + + //model constructor + _mainPS.printPrefixln("sc_application(sc_module_name name)"); + + //parameter of constructor + _mainPS.printPrefix(": sc_module(name)"); + for (Process p : x.getProcessList()) { + _mainPS.println(","); + _mainPS.printPrefix(p.getName() + "_ins(\"" + + p.getName() +"\")"); + } + + if (x.getChannelList().size() > 0) { + for (Channel c : x.getChannelList()) { + _mainPS.println(","); + _mainPS.printPrefix(c.getName() + "_ins(" + + "\"" + c.getName() + "\", " + + c.getSize() * c.getTokenSize() + + ")"); + } + } + _mainPS.println(""); + _mainPS.printLeftBracket(); + + //construtor content + //build the network + for (Channel ch : x.getChannelList()) { + ch.accept(this); + } + _mainPS.println(""); + + _mainPS.println(""); + + _mainPS.printPrefixln("SC_THREAD(thread_init);"); + //init thread + _mainPS.printPrefixln("SC_THREAD(thread_sched);"); + + //declare concurrent non-terminating threads + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln("SC_THREAD(thread_" + + p.getName() + ");"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + //define scheduler thread + _mainPS.printPrefixln("void thread_init()"); + _mainPS.printLeftBracket(); + //init + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln(p.getName() + "_ins.initialize();"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + + //different scheduling algorithm can be put here + _mainPS.printPrefixln("void thread_sched()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (1)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("for (iter=eventList.begin(); iter != " + + "eventList.end(); ++iter)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("sc_event* e = (*iter);"); + _mainPS.printPrefixln("e->notify();"); + _mainPS.printRightBracket(); + _mainPS.printPrefixln("eventList.clear();"); + _mainPS.printPrefixln("wait(sched_event);"); + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + _mainPS.println(); + + //define threads + for (Process p : x.getProcessList()) { + p.accept(this); + } + + /* begin of profiling: initialization function. */ + /* - opens file */ + /* - writes a list of all channels with the connected ports to the file. */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("void initialize_profiler()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("if ((profiler_output_file = fopen(PROFILER_OUTPUT_FILENAME,\"w\"))==NULL)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("printf(\"Unable to open profiler output file. No profiling output is written.\\n\");"); + _mainPS.printPrefixln("return;"); + _mainPS.printRightBracket(); + //_mainPS.printPrefixln("printf(\"Profiling data is written to %s.\\n\", PROFILER_OUTPUT_FILENAME);"); + _mainPS.println(); + + for (Channel ch : x.getChannelList()) { + String outputString = "fprintf(profiler_output_file, \"c " + ch.getName() + " " + ch.getSize(); + String outputStringAppendix = ""; + for (Port p : ch.getPortList()) { + Port peerPort = (Port)(p.getPeerPort()); + Resource peerResource = p.getPeerResource(); + + if (p.isOutPort()) { + // channel.out == process.in + outputString += " i " + peerResource.getName() + " %pI"; + outputStringAppendix += ",(" + peerResource.getName() + + "_ins.INPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), "") + ")"; + } else { + outputString += " o " + peerResource.getName() + " %pO"; + outputStringAppendix += ",(" + peerResource.getName() + + "_ins.OUTPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), "") + ")"; + } + } + outputString += "\\n\"" + outputStringAppendix + ");"; + _mainPS.printPrefixln(outputString); + } + _mainPS.printRightBracket(); + _mainPS.printPrefixln("#endif"); + _mainPS.println(); + /* end of profiling */ + + _mainPS.printRightBracket(); // end of class + _mainPS.println(";"); + + //create and run the simulator + _mainPS.printPrefixln("int sc_main (int argc, char *argv[])"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("sc_report_handler::set_actions(\"" + + "/IEEE_Std_1666/deprecated\", SC_DO_NOTHING);"); + _mainPS.printPrefixln("sc_report::register_id(" + + "5000, " + + "\"parameter problem\" );"); + + //create an instance of the application model + //remove potential whitespaces before using the process + //network name as a systemc identifier + _mainPS.printPrefixln("sc_application my_app_mdl(\"" + + x.getName().replaceAll(" ", "") + "\");"); + + /* begin of profiling: initialize the profiler */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("my_app_mdl.initialize_profiler();"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("sc_start(-1,SC_NS);"); + /* begin of profiling: close the output file */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fclose(profiler_output_file);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("return 0;"); + + _mainPS.printRightBracket(); + + } + catch (Exception e) { + System.out.println("HdsModuleVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Print a line for the process in the correct format for DOTTY. + * + * @param x process that needs to be rendered + */ + public void visitComponent(Process x) { + _mainPS.printPrefixln("void thread_" + x.getName() + "()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (!" + x.getName() + + "_ins.isDetached())"); + _mainPS.printLeftBracket(); + + /* begin of profiling: start event */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fprintf(profiler_output_file, \"%u "+ x.getName() + " started.\\n\", profiler_event_counter++);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln(x.getName() + "_ins.fire();"); + + /* begin of profiling: stop event */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fprintf(profiler_output_file, \"%u "+ x.getName() + " stopped.\\n\", profiler_event_counter++);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("eventList.push_back(&" + x.getName() + + "_event);"); + _mainPS.printPrefixln("sched_event.notify();"); + _mainPS.printPrefixln("wait(" + x.getName() + "_event);"); + + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + } + + /** + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + for (Port p : x.getPortList()) { + Port peerPort = (Port)(p.getPeerPort()); + Resource peerResource = p.getPeerResource(); + if (peerPort.getRange() != null) { + if (p.isOutPort()) { + _mainPS.printPrefix(peerResource.getName() + + "_ins.INPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), "")); + } + else if (p.isInPort()) { + _mainPS.printPrefix(peerResource.getName() + + "_ins.OUTPORT_" + + peerPort.getBasename() + + peerPort.getName().replaceAll( + "_([0-9]+)", "[$1]").replaceFirst( + peerPort.getBasename(), "")); + } + } + else { + if (p.isOutPort()) { + _mainPS.printPrefix(peerResource.getName() + + "_ins.INPORT_" + peerPort.getName()); + } + else if (p.isInPort()) { + _mainPS.printPrefix(peerResource.getName() + + "_ins.OUTPORT_" + peerPort.getName()); + } + } + _mainPS.println(" = &" + x.getName() + "_ins;"); + } + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/hds/HdsProcessVisitor.java b/dol/src/dol/visitor/hds/HdsProcessVisitor.java new file mode 100644 index 0000000..3912408 --- /dev/null +++ b/dol/src/dol/visitor/hds/HdsProcessVisitor.java @@ -0,0 +1,232 @@ +/* $Id: HdsProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $$ */ +package dol.visitor.hds; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a wrapper class for a process: process_wrapper.[h/cpp]. + */ +public class HdsProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public HdsProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param p process that needs to be rendered + */ + public void visitComponent(Process p) { + try { + _createCppFile(p); + _createHeaderFile(p); + _adaptSources(p); + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _adaptSources(Process p) throws IOException { + Sed sed = new Sed(); + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(). + replaceAll("(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (port.isOutPort()) { + sed.sed(processHeaderFile, + "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", + "$1 " + "((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->OUTPORT_" + + port.getBasename() + ")"); + + } + else if (port.isInPort()) { + sed.sed(processHeaderFile, + "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", + "$1 " + "((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->INPORT_" + + port.getBasename() + ")"); + } + } + } + } + + /** + * + */ + protected void _createCppFile(Process p) throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + String newline = System.getProperty("line.separator"); + String code = "#include \"" + p.getBasename() + "_wrapper.h\"" + + newline; + for (SourceCode sr : p.getSrcList()) { + code += "#include \"" + sr.getLocality() + "\"" + newline; + } + /* + for (SourceCode sr : p.getSrcList()) { + code += "#include \"" + sr.getLocality().substring(0, + sr.getLocality().lastIndexOf(".") + 1) + "h\"" + + newline; + } + */ + + code += newline; + + code += p.getBasename() + "_wrapper::" + + p.getBasename() + "_wrapper(char* name)" + + newline; + code += " : ProcessWrapper(name) {" + newline; + code += " _state = (LocalState)new " + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State;" + + newline; + code += " _process.local = _state;" + newline; + code += " _process.init = " + p.getBasename() + "_init;" + + newline; + code += " _process.fire = " + p.getBasename() + "_fire;" + + newline; + code += " _process.wptr = this;" + newline; + code += "}" + newline + newline; + + code += p.getBasename() + "_wrapper::~" + p.getBasename() + + "_wrapper() {" + newline; + code += " if (_state)" + newline; + code += " delete (" + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State*)_state;" + + newline; + code += "}" + newline; + ps.printPrefixln(code); + } + + protected void _createHeaderFile(Process p) + throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + String newline = System.getProperty("line.separator"); + String code = "#ifndef " + p.getBasename() + "_WRAPPER_H" + + newline; + code += "#define " + p.getBasename() + "_WRAPPER_H" + newline + + newline; + code += "#include \"ProcessWrapper.h\"" + newline; + code += "#include \"dolSupport.h\"" + newline; + code += newline; + + code += "class " + p.getBasename() + "_wrapper : " + + "public ProcessWrapper {" + newline; + code += " public:" + newline; + code += " " + p.getBasename() + + "_wrapper(char* name);" + newline; + code += " virtual ~" + p.getBasename() + "_wrapper();" + + newline; + + Vector portList = new Vector(); + for (Port port : p.getPortList()) { + String basename = port.getBasename(); + + if (!portList.contains(basename)) { + portList.add(basename); + + if (((Channel)port.getPeerResource()).getType(). + equals("wfifo")) { + code += " WindowedFifo *"; + } else { + code += " Fifo *"; + } + if (!port.getRange().equals("")) { + if (port.isOutPort()) { + code += "OUTPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];" + newline; + } + else if (port.isInPort()) { + code += "INPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];" + newline; + } + } + else { + if (port.isOutPort()) { + code += "OUTPORT_" + + port.getName() + ";" + newline; + } + else if (port.isInPort()) { + code += "INPORT_" + + port.getName() + ";" + newline; + } + } + } + } + + code += " protected:" + newline; + code += " LocalState _state;" + newline; + code += "};" + newline + newline; + code += "#endif"; + ps.printPrefixln(code); + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/hds/HdsVisitor.java b/dol/src/dol/visitor/hds/HdsVisitor.java new file mode 100644 index 0000000..bb8d47a --- /dev/null +++ b/dol/src/dol/visitor/hds/HdsVisitor.java @@ -0,0 +1,100 @@ +/* $Id: HdsVisitor.java 1 2010-02-24 13:03:05Z haidw $$ */ +package dol.visitor.hds; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a HdS package. + */ +public class HdsVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param packageName name of the HdS directory + */ + public HdsVisitor(String packageName) { + _packageName = packageName; + } + + /** + * + * @param x process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork x) { + try { + _generateDirHierarchy(); + + x.accept(new HdsMakefileVisitor(_srcDir)); + x.accept(new HdsModuleVisitor(_srcDir)); + x.accept(new HdsProcessVisitor(_wrapperDir)); + } + catch (Exception e) { + System.out.println("HdSVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _generateDirHierarchy() + throws IOException, FileNotFoundException { + File dir = new File(_packageName); + dir.mkdirs(); + + _srcDir = _packageName + _delimiter + _srcDirName; + dir = new File(_srcDir); + dir.mkdirs(); + + _libDir = _srcDir + _delimiter + _libDirName; + dir = new File(_libDir); + dir.mkdirs(); + + _processDir = _srcDir + _delimiter + _processDirName; + dir = new File(_processDir); + dir.mkdirs(); + + _wrapperDir = _srcDir + _delimiter + _wrapperDirName; + dir = new File(_wrapperDir); + dir.mkdirs(); + + //copy library files + File source = new File(_ui.getMySystemCLib(). + replaceAll("systemC", "hds")); + File destination = new File(_libDir); + new Copier().copy(source, destination); + + //copy process source code + source = new File(_srcDirName); + destination = new File(_processDir); + new Copier().copy(source, destination); + } + + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; + + protected String _libDir = ""; + protected static String _libDirName = "lib"; + + protected String _processDir = ""; + protected static String _processDirName = "processes"; + + protected String _wrapperDir = ""; + protected static String _wrapperDirName = "sc_wrappers"; + + protected String _threadPostfix = "_thread"; + + protected CodePrintStream _mainPS = null; +} diff --git a/dol/src/dol/visitor/hds/lib/Fifo.cpp b/dol/src/dol/visitor/hds/lib/Fifo.cpp new file mode 100644 index 0000000..4c66c4c --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/Fifo.cpp @@ -0,0 +1,254 @@ +#include "Fifo.h" + +/** + * + */ +Fifo::Fifo(char* name, unsigned size = 18) { + //std::cout << "Create Fifo." << std::endl; + //except at the beginning, _head and _tail must never overlap, + //otherwise one does not know whether the buffer is full or + //empty. to have nevertheless a buffer with the given capacity, + //a buffer with one more element is allocated. + _size = size; + _buffer = new char[_size]; + //_head = 0; + _use = 0; + _tail = 0; + _name = new char[strlen(name) + 1]; + strcpy(_name, name); +} + +/** + * + */ +Fifo::~Fifo() { + //std::cout << "Delete Fifo." << std::endl; + if (_buffer) { + delete _buffer; + } + if (_name) { + delete _name; + } + _buffer = 0; + //_head = 0; + _use = 0; + _tail = 0; + _name = 0; + //std::cout << "Deleted Fifo." << std::endl; +} + +/** + * + */ +unsigned Fifo::read(void *destination, unsigned len) { + char* buffer = (char*)destination; + unsigned read = 0; + //std::cout << "Try to read " << len << " bytes from Fifo " << _name << "." << std::endl; + + + while (read < len) { + while( _use < len) + wait(_writeEvent); + unsigned tocopy = (len - read <= _use ? len - read : _use); + + if ((unsigned)_tail + tocopy < _size) { + memcpy(buffer, _buffer + (unsigned)_tail, tocopy); + } + else { + memcpy(buffer, _buffer + (unsigned)_tail, _size - (unsigned)_tail); + memcpy(buffer + _size - (unsigned)_tail, _buffer, tocopy - _size + (unsigned)_tail); + } + + read += tocopy; + buffer += tocopy; + _tail = (_tail + tocopy) % _size; + _use -= read; + _readEvent.notify(); + } + + /*while (read < len) { + if (used() == 0) { + wait(_writeEvent); + } else if ((len - read) < used()) { + while (read < len) { + unsigned tocopy = (len - read + _tail >= _size) ? _size - _tail : len - read; + memcpy(buffer, _buffer + _tail, tocopy); + _tail = (_tail + tocopy) % _size; + read += tocopy; + buffer += tocopy; + } + _readEvent.notify(); + } else { + *buffer++ = *(_buffer + _tail % _size); + _tail = (_tail + 1) % _size; + read++; + _readEvent.notify(); + } + }*/ + + //std::cout << "Read " << read << " bytes from Fifo " << _name << "." << std::endl; + return read; +} + +/** + * + */ +unsigned Fifo::write(const void *source, unsigned len) { + char* buffer = (char*)source; + unsigned write = 0; + unsigned head = (_tail + _use) % _size; + //std::cout << "Try to write " << len << " bytes to Fifo " << _name << std::endl; + + while (write < len) { + while (unused() < len) + wait(_readEvent); + unsigned tocopy = (len - write <= unused() ? len - write : unused()); + + if (head + tocopy < _size) { + memcpy(_buffer + head, buffer, tocopy); + } + else { + memcpy(_buffer + head, buffer, _size - head); + memcpy(_buffer, buffer + _size - head, tocopy - _size + head); + } + + write += tocopy; + buffer += tocopy; + head = (head + tocopy) % _size; + _use += write; + _writeEvent.notify(); + } + + /* while (write < len) { + if (unused() == 0) { + wait(_readEvent); + } else if ((len - write) < unused()) { + while (write < len) { + unsigned tocopy = (len - write + _head >= _size) ? _size - _head : len - write; + memcpy(_buffer + _head, buffer, tocopy); + _head = (_head + tocopy) % _size; + write += tocopy; + buffer += tocopy; + } + _writeEvent.notify(); + } else { + *(_buffer + (unsigned)(_head) % _size) = *buffer++; + _head = (_head + 1) % _size; + write++; + _writeEvent.notify(); + } + }*/ + + //std::cout << "Wrote " << write << " bytes to Fifo " << _name << "." << std::endl; + return write; +} + +/** + * + */ +unsigned Fifo::size() const { + return (_size); +} + +/** + * + */ +unsigned Fifo::unused() const { + return (_size) - _use; +} + +/** + * + */ +unsigned Fifo::used() const { + return _use; +} + +/** + * + */ +char* Fifo::getName() const { + return _name; +} + +/** + * Test the implementation + */ +/* +class producer : public sc_module +{ + public: + Fifo *fifo; + + SC_HAS_PROCESS(producer); + + producer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + const char *str = + "Visit www.systemc.org and see what SystemC can do for you today!\n"; + + while (*str) { + fifo->write((void*)str++, 4); + } + } +}; + +class consumer : public sc_module +{ + public: + Fifo *fifo; + + SC_HAS_PROCESS(consumer); + + consumer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + char c; + cout << endl << endl; + + while (true) { + fifo->read(&c, 4); + cout << c << flush; + + if (fifo->used() == 1) + cout << "<1>" << flush; + if (fifo->used() == 9) + cout << "<9>" << flush; + } + } +}; + +class top : public sc_module +{ + public: + Fifo *fifo_inst; + producer *prod_inst; + consumer *cons_inst; + + top(sc_module_name name) : sc_module(name) + { + fifo_inst = new Fifo("myfifo", 10); + + prod_inst = new producer("producer"); + prod_inst->fifo = fifo_inst; + + cons_inst = new consumer("consumer"); + cons_inst->fifo = fifo_inst; + } +}; + +int sc_main (int argc , char *argv[]) { + top top1("top"); + sc_start(); + return 0; +} +*/ diff --git a/dol/src/dol/visitor/hds/lib/Fifo.h b/dol/src/dol/visitor/hds/lib/Fifo.h new file mode 100644 index 0000000..e694612 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/Fifo.h @@ -0,0 +1,29 @@ +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include "systemc.h" + +class Fifo { + public: + Fifo(char* name, unsigned size); + virtual ~Fifo(); + + virtual unsigned read(void *destination, unsigned len); + virtual unsigned write(const void *source, unsigned len); + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + virtual char* getName() const; + + protected: + char *_buffer; + //unsigned _head; + unsigned _tail; + unsigned _size; + unsigned _use; + char *_name; + sc_event _readEvent; + sc_event _writeEvent; +}; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/Performance_Extraction.cpp b/dol/src/dol/visitor/hds/lib/Performance_Extraction.cpp new file mode 100644 index 0000000..9322648 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/Performance_Extraction.cpp @@ -0,0 +1,447 @@ +#include "Performance_Extraction.h" +#include +#include + + +Performance_Extraction performance_extraction("static_characterization.xml"); + +/** + * + */ +int get_current_time(CURRENT_TIME *current_time_ptr) +{ + return clock_gettime(CLOCK_REALTIME, &(current_time_ptr->ts)); +} + + +/** + * + */ +double sub_time(CURRENT_TIME *start_time_ptr, CURRENT_TIME *end_time_ptr) +{ + CURRENT_TIME diff_time; + double time_ns; + + diff_time.ts.tv_sec = end_time_ptr->ts.tv_sec - start_time_ptr->ts.tv_sec; + diff_time.ts.tv_nsec = end_time_ptr->ts.tv_nsec - start_time_ptr->ts.tv_nsec; + time_ns = diff_time.ts.tv_sec * 1E+9 + diff_time.ts.tv_nsec; + + return time_ns; +} + + +/** + * + */ +Process_Performance::Process_Performance(const char *name) +{ + strcpy(_process_name, name); + _head = NULL; + _tail = NULL; +} + + +/** + * + */ +Process_Performance::~Process_Performance() +{ + COMP_ENTRY *temp; + DBGPRINT; + while (_head != NULL) { + temp = _head; + _head = _head->next; + delete temp; + } +} + + +/** + * + */ +const char *Process_Performance::get_name() +{ + return _process_name; +} + + +/** + * + */ +COMP_ENTRY *Process_Performance::get_head_entry() +{ + return _head; +} + + +/** + * + */ +COMP_ENTRY *Process_Performance::get_entry(int start_line, int end_line) +{ + COMP_ENTRY *temp = NULL; + temp = _head; + while (temp != NULL) { + if ((temp->start_line == start_line) && (temp->end_line == end_line)) { + break; + } + temp = temp->next; + } + + return temp; +} + + +/** + * + */ +COMP_ENTRY *Process_Performance::add_entry(int start_line, int end_line) +{ + COMP_ENTRY *temp = NULL; + temp = get_entry(start_line, end_line); + if (temp == NULL) { + temp = new COMP_ENTRY; + + if (_head == NULL) { + _head = temp; + _tail = temp; + temp->next = NULL; + } else { + _tail->next = temp; + _tail = temp; + temp->next = NULL; + } + temp->start_line = start_line; + temp->end_line = end_line; + temp->total_computation_time = 0; + temp->called_times = 0; + } + + return temp; +} + + +/** + * + */ +int Process_Performance::set_entry(int start_line, int end_line, + CURRENT_TIME *start_time_ptr, + CURRENT_TIME *end_time_ptr) +{ + int ret = 0; + COMP_ENTRY *temp; + double computation_time; + computation_time = sub_time(start_time_ptr, end_time_ptr); + temp = get_entry(start_line, end_line); + if (temp == NULL) { + temp = add_entry(start_line, end_line); + } + + temp->total_computation_time += computation_time; + temp->called_times++; + + return ret; +} + + +/** + * + */ +Performance_Extraction::Performance_Extraction(const char *chr_file_name) +{ + strcpy(_chr_file_name, chr_file_name); +} + + +/** + * + */ +Performance_Extraction::~Performance_Extraction() +{ + Process_Performance *process_performance_ptr; + +#ifdef INCLUDE_PERFORMANCE + //strcpy(_processor_type, "DSP"); + //add_to_xml_file(_chr_file_name); + //strcpy(_processor_type, "RISC"); + //add_to_xml_file(_chr_file_name); + + write_to_xml_file(_chr_file_name); +#endif + + for (_iter_process_performance = _list_process_performance.begin(); + _iter_process_performance != _list_process_performance.end(); + _iter_process_performance++) + { + process_performance_ptr = *_iter_process_performance; + delete process_performance_ptr; + } + _list_process_performance.clear(); + +} + + +/** + * + */ +int Performance_Extraction::add_computation_performance( + const char *process_name, int start_line, int end_line, + CURRENT_TIME *start_time_ptr, CURRENT_TIME *end_time_ptr) +{ + int ret = 0; + Process_Performance *process_performance_ptr; + + process_performance_ptr = get_process_performance(process_name); + if (process_performance_ptr == NULL) { + process_performance_ptr = new Process_Performance(process_name); + _list_process_performance.push_back(process_performance_ptr); + } + + process_performance_ptr->set_entry(start_line, end_line, + start_time_ptr, end_time_ptr); + + return ret; +} + + +/** + * + */ +Process_Performance *Performance_Extraction::get_process_performance( + const char *process_name) +{ + Process_Performance *process_performance_ptr = NULL; + + for (_iter_process_performance = _list_process_performance.begin(); + _iter_process_performance != _list_process_performance.end(); + _iter_process_performance++) { + process_performance_ptr = *_iter_process_performance; + if (strcmp(process_performance_ptr->get_name(), process_name) == 0) { + break; + } + } + + if (_iter_process_performance == _list_process_performance.end()) { + process_performance_ptr = NULL; + } + + return process_performance_ptr; +} + + +/** + * write performance data into XML file. (identical to add_to_xml_file() + * if file does not exist). + */ +int Performance_Extraction::write_to_xml_file(const char *chr_file_name) +{ + char start_line_str[NAME_LENGTH]; + char end_line_str[NAME_LENGTH]; + char computation_time_str[NAME_LENGTH]; + double computation_time; + Process_Performance *process_performance_ptr; + COMP_ENTRY *comp_entry; + + //create xml + string text = "\n\n"; + for (_iter_process_performance = _list_process_performance.begin(); + _iter_process_performance != _list_process_performance.end(); + _iter_process_performance++) { + process_performance_ptr = *_iter_process_performance; + + text += " get_name(); + text += "\">\n"; + + for (comp_entry = process_performance_ptr->get_head_entry(); + comp_entry != NULL; + comp_entry = comp_entry->next) { + sprintf(start_line_str, "%d", comp_entry->start_line); + sprintf(end_line_str, "%d", comp_entry->end_line); + + //hack to eliminate the overhead of system call clock_gettime. + //computation_time is nano sec. To get cycles, need to mutiply + //by the frequency of cpu. result is in cycles. + computation_time = (comp_entry->total_computation_time + / comp_entry->called_times - SYS_OVERHEAD) + * HOST_FREQUENCY; + if (computation_time < 0) { + computation_time = 0; + } + sprintf(computation_time_str, "%.0f", computation_time); + + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + } + text += " \n"; + } + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + text += " \n"; + text += "\n"; + + //write xml to file + std::ofstream out(chr_file_name, ios::out); + if (!out) { + printf("Cannot open file %s. Return.\n", chr_file_name); + return -1; + } + out.write(text.c_str(), text.size()); + out.close(); + return 0; +} + + +/** + * add performance data to existing XML file (or create new one if file + * does not exist). + */ +int Performance_Extraction::add_to_xml_file(const char *chr_file_name) +{ + int ret = 0; + XMLNode file_node; + XMLNode root_node; + XMLNode process_node; + XMLNode comp_node; + int computation_index = 0; + XMLNode processor_node; + XMLNode read_node; + XMLNode write_node; + + Process_Performance *process_performance_ptr; + COMP_ENTRY *comp_entry; + + char start_line_str[NAME_LENGTH]; + char end_line_str[NAME_LENGTH]; + char computation_time_str[NAME_LENGTH]; + double computation_time; + + file_node = XMLNode::parseFile(chr_file_name); + if (file_node.isEmpty()) { + file_node = XMLNode::createXMLTopNode("xml", TRUE); + file_node.addAttribute("version", "1.0"); + root_node = file_node.addChild("characterization"); + } + + root_node = file_node.getChildNode(); + if (root_node.isEmpty()) { + ret = -1; + printf("Open characterization file error\n"); + return ret; + } + + for (_iter_process_performance = _list_process_performance.begin(); + _iter_process_performance != _list_process_performance.end(); + _iter_process_performance++) { + process_performance_ptr = *_iter_process_performance; + + printf("%s\n", process_performance_ptr->get_name()); + + process_node = root_node.getChildNodeWithAttribute( + "process", "name", process_performance_ptr->get_name()); + if (process_node.isEmpty()) + { + process_node = root_node.addChild("process"); + process_node.addAttribute( + "name",process_performance_ptr->get_name()); + } + + for (comp_entry = process_performance_ptr->get_head_entry(); + comp_entry != NULL; + comp_entry = comp_entry->next) { + sprintf(start_line_str, "%d", comp_entry->start_line); + sprintf(end_line_str, "%d", comp_entry->end_line); + + printf("%d %d\n", comp_entry->start_line, comp_entry->end_line); + + /* Hack to eliminate the overhead of system call clock_gettime. + computation_time is nano sec. To get cycles, need to mutiply + by the frequency of cpu. + result is cycles */ + computation_time = (comp_entry->total_computation_time + / comp_entry->called_times - SYS_OVERHEAD) + * HOST_FREQUENCY; + + if (computation_time < 0) { + computation_time = 0; + } + + sprintf(computation_time_str, "%.0f", computation_time); + computation_index = 0; + comp_node = process_node.getChildNode("computation", + computation_index++); + while (!comp_node.isEmpty()) { + if (strcmp(comp_node.getAttribute("start"),start_line_str)==0 + && strcmp(comp_node.getAttribute("end"),end_line_str)==0) { + break; + } + + comp_node = process_node.getChildNode("computation", + computation_index++); + } + + if (comp_node.isEmpty()) { + comp_node = process_node.addChild("computation"); + comp_node.addAttribute("start", start_line_str); + comp_node.addAttribute("end", end_line_str); + } + + processor_node = comp_node.getChildNodeWithAttribute("processor", "type", _processor_type); + if (processor_node.isEmpty()) { + processor_node = comp_node.addChild("processor"); + processor_node.addAttribute("type", _processor_type); + processor_node.addAttribute("time", computation_time_str); + } + } + } + + read_node = root_node.getChildNodeWithAttribute("communication", + "name", "read"); + if (read_node.isEmpty()) { + read_node = root_node.addChild("communication"); + read_node.addAttribute("name", "read"); + } + + processor_node = read_node.getChildNodeWithAttribute("processor", + "type", + _processor_type); + if (processor_node.isEmpty()) { + processor_node = read_node.addChild("processor"); + processor_node.addAttribute("type", _processor_type); + processor_node.addAttribute("time", "2"); + } + + write_node = root_node.getChildNodeWithAttribute("communication", + "name", "write"); + if (write_node.isEmpty()) { + write_node = root_node.addChild("communication"); + write_node.addAttribute("name", "write"); + } + + processor_node = write_node.getChildNodeWithAttribute("processor", + "type", + _processor_type); + if (processor_node.isEmpty()) { + processor_node = write_node.addChild("processor"); + processor_node.addAttribute("type", _processor_type); + processor_node.addAttribute("time", "2"); + } + + file_node.writeToFile(chr_file_name); + return 0; +} diff --git a/dol/src/dol/visitor/hds/lib/Performance_Extraction.h b/dol/src/dol/visitor/hds/lib/Performance_Extraction.h new file mode 100644 index 0000000..f122696 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/Performance_Extraction.h @@ -0,0 +1,67 @@ +#ifndef _PERFORMANCE_EXTRACTION_H +#define _PERFORMANCE_EXTRACTION_H + +#include +#include +#include "trace.h" + +#define SYS_OVERHEAD 1400 //what should be exact vale +#define HOST_FREQUENCY 3 //Ghz + +typedef struct COMP_ENTRY { + int start_line; + int end_line; + double total_computation_time; + int called_times; + COMP_ENTRY *next; +} COMP_ENTRY; + +typedef struct CURRENT_TIME { + struct timespec ts; +}CURRENT_TIME; + +extern int get_current_time(CURRENT_TIME *current_time_ptr); +extern double sub_time(CURRENT_TIME *start_time_ptr, CURRENT_TIME *end_time_ptr); + +class Process_Performance +{ +private: + char _process_name[NAME_LENGTH]; + COMP_ENTRY *_head; + COMP_ENTRY *_tail; + +public: + Process_Performance(const char *process_name); + ~Process_Performance(); + + const char *get_name(); + COMP_ENTRY * add_entry(int start_line, int end_line); + int set_entry(int start_line, int end_line, CURRENT_TIME *start_time_ptr, + CURRENT_TIME *end_time_ptr); + COMP_ENTRY *get_entry(int start_line, int end_line); + COMP_ENTRY *get_head_entry(); +}; + +class Performance_Extraction +{ +private: + char _processor_type[NAME_LENGTH]; + char _chr_file_name[NAME_LENGTH]; + list _list_process_performance; + list::iterator _iter_process_performance; + +public: + Performance_Extraction(const char *chr_file_name); + ~Performance_Extraction(); + + int add_computation_performance(const char *process_name, int start_line, + int end_line, CURRENT_TIME *start_time_ptr, + CURRENT_TIME *end_time_ptr); + Process_Performance *get_process_performance(const char *process_name); + int write_to_xml_file(const char *chr_file_name); + int add_to_xml_file(const char *chr_file_name); +}; + +extern Performance_Extraction performance_extraction; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/ProcessWrapper.cpp b/dol/src/dol/visitor/hds/lib/ProcessWrapper.cpp new file mode 100644 index 0000000..89f413a --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/ProcessWrapper.cpp @@ -0,0 +1,157 @@ +#include "ProcessWrapper.h" +#include "dolSupport.h" + +/** + * + */ +ProcessWrapper::ProcessWrapper( + sc_module_name name = sc_gen_unique_name("process")) + : sc_module(name) { + + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + + _isDetached = false; + for (int i = 0; i < 4; i++) { + _iteratorIndex[i] = getIndex(_name, "_", i); + } +} + +/** + * + */ +ProcessWrapper::~ProcessWrapper() { + if (_name) { + delete _name; + } +} + +/** + * + */ +void ProcessWrapper::initialize() { + _process.init(&_process); +} + +/** + * + */ +int ProcessWrapper::fire() +{ + int returnValue; + + #ifdef INCLUDE_TRACE + start_line = -1; + #endif + + #ifdef INCLUDE_PERFORMANCE + start_line = -1; + get_current_time(&start_time); + #endif + + returnValue = _process.fire(&_process); + + #ifdef INCLUDE_TRACE + end_line = -1; + dol_functional_trace.create_computation_event(basename(), + start_line, end_line); + #endif + + #ifdef INCLUDE_PERFORMANCE + get_current_time(&end_time); + end_line = -1; + performance_extraction.add_computation_performance(basename(), + start_line, end_line, &start_time, &end_time); + #endif + + return returnValue; +} + + +/** + * + */ +void ProcessWrapper::detach() { + _isDetached = true; +} + + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(const char* string, char* tokens, + int indexNumber) const { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + return -1; +} + + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} + + +/** + * Get the name of this process. + */ +char* ProcessWrapper::getName() const { + return _name; +} + + +/** + * + */ +#ifdef INCLUDE_PROFILER +void ProcessWrapper::addToProfile(const char *event, void *port, + int length) { + if (profiler_output_file != NULL) { + fprintf(profiler_output_file, "%u %s %s %p%s %d\n", + profiler_event_counter++, _name, event, port, (event[0] == 'r') ? "I" : "O", + length); + + } else { + printf("profiler_output_file does not exist"); + } +} +#endif diff --git a/dol/src/dol/visitor/hds/lib/ProcessWrapper.h b/dol/src/dol/visitor/hds/lib/ProcessWrapper.h new file mode 100644 index 0000000..76ab3b3 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/ProcessWrapper.h @@ -0,0 +1,58 @@ +#ifndef _PROCESSWRAPPER_H_ +#define _PROCESSWRAPPER_H_ + +#include "systemc.h" +#include "dol.h" +#include "dol_sched_if.h" +#include "Fifo.h" +#include "WindowedFifo.h" +#include "functional_trace.h" +#include "Performance_Extraction.h" + + +#ifdef INCLUDE_PROFILER +extern FILE *profiler_output_file; +extern unsigned int profiler_event_counter; +#endif + +class ProcessWrapper : virtual public dol_sched_if, public sc_module +{ + public: + ProcessWrapper(sc_module_name name); + virtual ~ProcessWrapper(); + virtual void initialize(); + virtual int fire(); + virtual bool isDetached() { return _isDetached; } + virtual void detach(); + virtual int getIndex(unsigned indexNumber) const; + virtual char* getName() const; + +#ifdef INCLUDE_PROFILER + virtual void addToProfile(const char *event, void *port, + int length); +#endif + +#ifdef INCLUDE_TRACE + int start_line; + int end_line; + char channel_name[NAME_LENGTH]; +#endif + +#ifdef INCLUDE_PERFORMANCE + int start_line; + int end_line; + CURRENT_TIME start_time; + CURRENT_TIME end_time; +#endif + + protected: + char* _name; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; + virtual int getIndex(const char* string, char* tokens, + int indexNumber) const; +}; + +#endif + diff --git a/dol/src/dol/visitor/hds/lib/WindowedFifo.cpp b/dol/src/dol/visitor/hds/lib/WindowedFifo.cpp new file mode 100644 index 0000000..50e960a --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/WindowedFifo.cpp @@ -0,0 +1,288 @@ +#include "WindowedFifo.h" + +/** + * + */ +WindowedFifo::WindowedFifo(char* name, unsigned size = 20) { + //std::cout << "Create WindowedFifo." << std::endl; + _size = size; + _buffer = new char[_size]; + _head = 0; + _tail = 0; + _headRoom = 0; + _tailRoom = 0; + _use = 0; + //indicates whether Fifo is empty or full if _head == _tail + //_isFull = false; + _isHeadReserved = false; + _isTailReserved = false; + _name = new char[strlen(name) + 1]; + strcpy(_name, name); +} + +/** + * + */ +WindowedFifo::~WindowedFifo() { + //std::cout << "Delete WindowedFifo." << std::endl; + if (_buffer) { + delete _buffer; + } + if (_name) { + delete _name; + } + _buffer = 0; + _head = 0; + _tail = 0; + _name = 0; + _use = 0; + //std::cout << "Deleted WindowedFifo." << std::endl; +} + +/** + * + */ +unsigned WindowedFifo::reserve(void** dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to reserve " << len << " bytes." << std::endl; + + //can only reserve once piece at a time + if (_isHeadReserved) { + *destination = 0; + return 0; + } + + while (unused() == 0) { + wait(_readEvent); + } + + //reserve at most as much memory as still available in the buffer + unsigned write = (len <= _size - _use ? len : _size - _use); + + if (write > 0) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_head + write > _size) { + write = _size - _head; + } + + _headRoom = (_head + write) == _size? 0 : _head + write; + *destination = &(_buffer[_head]); + _isHeadReserved = true; + + //the following comparison is unsafe in a multi-threaded + //environment and potentially leads to race-conditions + /*if (_headRoom == _tail) { + _isFull = true; + } else { + _isFull = false; + }*/ + } + + //std::cout << "Reserved " << write << " bytes." << std::endl; + _writeReserve = write; + return write; +} + +/** + * + */ +void WindowedFifo::release() { + if (_isHeadReserved) { + //std::cout << "Released " << _headRoom - _head << " bytes." << std::endl; + _head = _headRoom; + _use += _writeReserve; + _isHeadReserved = false; + _writeEvent.notify(); + } +} + +/** + * + */ +unsigned WindowedFifo::capture(void **dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to capture " << len << " bytes." << std::endl; + + if (_isTailReserved) { + //std::cout << "Only one attempt to capture allowed." << std::endl; + *destination = 0; + return 0; + } + + while (used() == 0) { + wait(_writeEvent); + } + + //capture at most as much data as available in the buffer + unsigned read = (len <= _use ? len : _use); + + if ( read > 0 ) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_tail + read> _size) { + read = _size - _tail; + } + + _tailRoom = (_tail + read) == _size ? 0 : _tailRoom = _tail + read; + *destination = &(_buffer[_tail]); + _isTailReserved = true; + } + + //std::cout << "Captured " << read << " bytes." << std::endl; + + _readReserve = read; + return read; +} + +/** + * + */ +void WindowedFifo::consume() { + if (_isTailReserved) { + //std::cout << "Consumed " << _tailRoom - _tail << " bytes." << std::endl; + _tail = _tailRoom; + _use -= _readReserve; + _isTailReserved = false; + //_isFull = false; + _readEvent.notify(); + } +} + +/** + * + */ +unsigned WindowedFifo::size() const { + return _size; +} + +/** + * + */ +unsigned WindowedFifo::unused() const { + return _size - _use; +} + +/** + * + */ +unsigned WindowedFifo::used() const { + return _use; + /*if (_headRoom > _tail) { + return _headRoom - _tail; + } else if (_headRoom == _tail) { + if (_isFull == true) { + return _size; + } else { + return 0; + } + } + return _headRoom + _size - _tail;*/ +} + +/** + * + */ +char* WindowedFifo::getName() const { + return _name; +} + +/** + * Test the implementation + */ +/* +#include +#include +#define LENGTH 10 + +class producer : public sc_module +{ + public: + WindowedFifo *wfifo; + + SC_HAS_PROCESS(producer); + + producer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + for (int j = 0; j < LENGTH; j++) { + //std::cout << "write " << i << " to Fifo. "; + int *buf1; + int write = wfifo->reserve((void**)&buf1, sizeof(int)); + + if (write == sizeof(int)) { + *buf1 = j; + wfifo->release(); + //std::cout << "used: " << std::setw(2) << wfifo->used() + // << ", unused: " << std::setw(2) << wfifo->unused() + // << ", size: " << std::setw(2) << wfifo->size() + // << std::endl; + } else { + std::cout << "Not successful: " << write << std::endl; + } + } + printf("producer returns.\n"); + } +}; + +class consumer : public sc_module +{ + public: + WindowedFifo *wfifo; + + SC_HAS_PROCESS(consumer); + + consumer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + for (int j = 0; j < LENGTH; j++) { + int* buf3; + int read = wfifo->capture((void**)&buf3, sizeof(int)); + if (read == sizeof(int)) { + std::cout << "read " << (unsigned)*buf3 << " from WindowedFifo "; + std::cout << "used: " << std::setw(2) << wfifo->used() + << ", unused: " << std::setw(2) << wfifo->unused() + << ", size: " << std::setw(2) << wfifo->size() + << std::endl; + wfifo->consume(); + } else { + std::cout << "Read nothing from WindowedFifo." << std::endl; + } + } + printf("consumer returns.\n"); + } +}; + +class top : public sc_module +{ + public: + WindowedFifo *wfifo_inst; + producer *prod_inst; + consumer *cons_inst; + + top(sc_module_name name) : sc_module(name) + { + wfifo_inst = new WindowedFifo("myfifo", 4); + + prod_inst = new producer("producer"); + prod_inst->wfifo = wfifo_inst; + + cons_inst = new consumer("consumer"); + cons_inst->wfifo = wfifo_inst; + } +}; + +int sc_main (int argc , char *argv[]) { + top top1("top"); + sc_start(); + return 0; +} +*/ diff --git a/dol/src/dol/visitor/hds/lib/WindowedFifo.h b/dol/src/dol/visitor/hds/lib/WindowedFifo.h new file mode 100644 index 0000000..a223c95 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/WindowedFifo.h @@ -0,0 +1,40 @@ +#ifndef _WINDOWEDFIFO_H_ +#define _WINDOWEDFIFO_H_ + +#include "systemc.h" + +class WindowedFifo { + public: + WindowedFifo(char* name, unsigned size); + virtual ~WindowedFifo(); + + virtual unsigned reserve(void** destination, unsigned len); + virtual void release(); + + virtual unsigned capture(void** destination, unsigned len); + virtual void consume(); + + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + virtual char* getName() const; + + protected: + char *_buffer; + unsigned _head; + unsigned _tail; + unsigned _headRoom; + unsigned _tailRoom; + unsigned _size; + unsigned _use; + unsigned _writeReserve; + unsigned _readReserve; + //bool _isFull; + bool _isHeadReserved; + bool _isTailReserved; + char *_name; + sc_event _readEvent; + sc_event _writeEvent; +}; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/dol.h b/dol/src/dol/visitor/hds/lib/dol.h new file mode 100644 index 0000000..8fbefe4 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/dol.h @@ -0,0 +1,39 @@ +#ifndef DOL_H +#define DOL_H + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * Define the DOL process handler scheme. + * - Local variables are defined in structure LocalState. Local + * variables may vary from different processes. + * - The ProcessInit function pointer points to a function which + * initializes a process. + * - The ProcessFire function pointer points to a function which + * performs the actual computation. The communication between + * processes is inside the ProcessFire function. + * - The WPTR is a placeholder for callback. One can just + * leave it blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/dolSupport.cpp b/dol/src/dol/visitor/hds/lib/dolSupport.cpp new file mode 100644 index 0000000..1db732a --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/dolSupport.cpp @@ -0,0 +1,149 @@ +#include "dolSupport.h" +#include "ProcessWrapper.h" + +/** + * + */ +unsigned write(void *port, void *buf, unsigned len, DOLProcess *process) +{ + Fifo *fifo = static_cast(port); + char *str = static_cast(buf); + #ifdef INCLUDE_PROFILER + (static_cast(process->wptr))->addToProfile( + "w", port, len); + #endif + fifo->write((void*)str, len); + #ifdef INCLUDE_TRACE + strcpy((static_cast(process->wptr))->channel_name, + fifo->getName()); + #endif + return len; +} + + +/** + * + */ +unsigned read(void *port, void *buf, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + char *str = static_cast(buf); + #ifdef INCLUDE_PROFILER + (static_cast(process->wptr))->addToProfile( + "r", port, len); + #endif + fifo->read((void*)str, len); + #ifdef INCLUDE_TRACE + strcpy((static_cast(process->wptr))->channel_name, + fifo->getName()); + #endif + return len; +} + + +/** + * + */ +int wtest(void *port, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + return (fifo->unused() >= len) ? 1 : 0; +} + + +/** + * + */ +int rtest(void *port, unsigned len, DOLProcess *process) { + Fifo *fifo = static_cast(port); + return (fifo->used() >= len) ? 1 : 0; +} + + +/** + * + */ +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->reserve(destination, len); +} + +/** + * + */ +void release(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->release(); +} + +/** + * + */ +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->capture(destination, len); +} + +/** + * + */ +void consume(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->consume(); +} + + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast(p->wptr)->detach(); +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0) { + *port = (void**)((void**)base)[index0]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1) { + *port = (void**)((void**)base)[index0 * range1 + index1]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2) { + *port = (void**)((void**)base)[index0 * range1 * range2 + + index1 * range2 + index2]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3) { + *port = (void**)((void**)base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + + index2 * range3 + + index3]; +} diff --git a/dol/src/dol/visitor/hds/lib/dolSupport.h b/dol/src/dol/visitor/hds/lib/dolSupport.h new file mode 100644 index 0000000..5f0505c --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/dolSupport.h @@ -0,0 +1,148 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include "dol.h" +#include "Fifo.h" +#include "functional_trace.h" +#include "Performance_Extraction.h" + +#ifdef INCLUDE_PERFORMANCE +#define DOL_write(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + end_time));\ + performance_extraction.add_computation_performance(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line,\ + &((static_cast(p->wptr))->start_time),\ + &((static_cast(p->wptr))->end_time));\ + write(port, buf, len, process);\ + (static_cast(p->wptr))->start_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + start_time)); } +#define DOL_read(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + end_time));\ + performance_extraction.add_computation_performance(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line,\ + &((static_cast(p->wptr))->start_time),\ + &((static_cast(p->wptr))->end_time));\ + read(port, buf, len, process);\ + (static_cast(p->wptr))->start_line = __LINE__;\ + get_current_time(&((static_cast(p->wptr))->\ + start_time)); } +#elif INCLUDE_TRACE +#define DOL_write(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + dol_functional_trace.create_computation_event(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line);\ + write(port, buf, len, process);\ + dol_functional_trace.create_write_event(\ + (static_cast(p->wptr))->basename(), len,\ + (static_cast(p->wptr))->channel_name);\ + (static_cast(p->wptr))->start_line = __LINE__; } +#define DOL_read(port, buf, len, process) {\ + (static_cast(p->wptr))->end_line = __LINE__;\ + dol_functional_trace.create_computation_event(\ + (static_cast(p->wptr))->basename(),\ + (static_cast(p->wptr))->start_line,\ + (static_cast(p->wptr))->end_line);\ + read(port, buf, len, process);\ + dol_functional_trace.create_read_event(\ + (static_cast(p->wptr))->basename(), len,\ + (static_cast(p->wptr))->channel_name);\ + (static_cast(p->wptr))->start_line = __LINE__; } +#else + #define DOL_write(port, buf, len, process) \ + write(port, buf, len, process) + #define DOL_read(port, buf, len, process) \ + read(port, buf, len, process) +#endif + +#define DOL_reserve(port, buf, size, process) \ + reserve(port, (void**)buf, size, process); + +#define DOL_release(port, process) \ + release(port, process); + +#define DOL_capture(port, buf, size, process) \ + capture(port, (void**)buf, size, process); + +#define DOL_consume(port, process) \ + consume(port, process); + +#define DOL_wtest(port, len, process) wtest(port, len, process) + +#define DOL_rtest(port, len, process) rtest(port, len, process) + +void DOL_detach(DOLProcess* p); + +//fifo access functions +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p); +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p); +int wtest(void *port, unsigned len, DOLProcess *process); +int rtest(void *port, unsigned len, DOLProcess *process); + +//windowed fifo access functions +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p); +void release(void* fifo, DOLProcess* p); +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p); +void consume(void* fifo, DOLProcess* p); + + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3); + +#define GETINDEX(dimension) \ + static_cast(p->wptr)->getIndex(dimension) + +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) Fifo *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#endif diff --git a/dol/src/dol/visitor/hds/lib/dol_sched_if.h b/dol/src/dol/visitor/hds/lib/dol_sched_if.h new file mode 100644 index 0000000..d861b1e --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/dol_sched_if.h @@ -0,0 +1,43 @@ +/************************************************************************** + dol_sched_if.h + + Scheduler interface for a DOL process + + (c) 2006 by Alexander Maxiaguine + + Computer Engineering and Networks Laboratory, TIK + Swiss Federal Institute of Technology, ETHZ Zurich + Switzerland + +**************************************************************************/ + +/************************************************************************** + Change Log: + + 14.03.06 -- creation + +**************************************************************************/ + +#ifndef DOL_SCHED_IF_H +#define DOL_SCHED_IF_H + +class dol_sched_if +{ + +public: + virtual void initialize() = 0; + virtual int fire() = 0; + + +protected: + dol_sched_if() {} + + +private: + + // disabled + dol_sched_if( const dol_sched_if& ); + dol_sched_if& operator = ( const dol_sched_if& ); +}; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/functional_trace.cpp b/dol/src/dol/visitor/hds/lib/functional_trace.cpp new file mode 100644 index 0000000..efca06c --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/functional_trace.cpp @@ -0,0 +1,295 @@ +#include "functional_trace.h" + +functional_trace dol_functional_trace; + +functional_trace::functional_trace() +{ + //DBGPRINT; + strcpy(trace_file_name, "trace1.txt"); + event_num = 0; + file_index = 1; +} + +functional_trace::~functional_trace() +{ + //DBGPRINT; + char temp[NAME_LENGTH]; +#ifdef INCLUDE_TRACE + strcpy(trace_file_name, "trace"); + sprintf(temp, "%d", file_index); + strcat(trace_file_name, temp); + strcat(trace_file_name, ".txt"); + write_to_file(trace_file_name); + free_traces(); + //printf("The traces in the memory have been freed\n"); +#endif + //DBGPRINT; +} + +void functional_trace::add_event_node(TRACE_EVENT &trace_event) +{ + Process_Trace *process_trace_ptr; + char temp[NAME_LENGTH]; + + process_trace_ptr = get_process_trace(trace_event.process_name); + if (process_trace_ptr == NULL) + { + process_trace_ptr = new Process_Trace(trace_event.process_name); + _list_process_trace.push_back(process_trace_ptr); + } + + process_trace_ptr->add_entry(&trace_event); + + event_num++; + if (event_num > 8000000) + { + strcpy(trace_file_name, "trace"); + sprintf(temp, "%d", file_index); + strcat(trace_file_name, temp); + strcat(trace_file_name, ".txt"); + write_to_file(trace_file_name); + free_traces(); + file_index++; + event_num = 0; + } + +} + +/* +void functional_trace::add_event_node(TRACE_EVENT &trace_event) +{ + XMLNode process_node; + char temp[NAME_LENGTH]; + process_node = xml_main_node.getChildNode(trace_event.process_name); + + if(process_node.isEmpty()) + { + process_node = xml_main_node.addChild(trace_event.process_name); + } + + XMLNode event_node = process_node.addChild("event"); + + event_node.addAttribute("type", trace_event_type_string[trace_event.event_type]); + + if(trace_event.event_type == COMPUTATION_EVENT) + { + + sprintf(temp, "%d", trace_event.computation_start_line); + event_node.addAttribute("start", temp); + sprintf(temp, "%d", trace_event.computation_end_line); + event_node.addAttribute("end", temp); + } + else if((trace_event.event_type == READ_EVENT) || (trace_event.event_type == WRITE_EVENT)) + { + sprintf(temp, "%d", trace_event.data_num); + event_node.addAttribute("data_num", temp); + event_node.addAttribute("channel_name", trace_event.channel_name); + } +} +*/ + +void functional_trace::create_computation_event(const char *process_name, int start_line, int end_line) +{ + TRACE_EVENT trace_event; + + trace_event.event_type = COMPUTATION_EVENT; + strcpy(trace_event.process_name, process_name); + trace_event.computation_start_line = start_line; + trace_event.computation_end_line = end_line; + strcpy(trace_event.channel_name, ""); + trace_event.data_num = 0; + + add_event_node(trace_event); +} + +void functional_trace::create_read_event(const char *process_name, int data_num, const char *channel_name) +{ + //DBGPRINT; + TRACE_EVENT trace_event; + trace_event.event_type = READ_EVENT; + strcpy(trace_event.process_name, process_name); + trace_event.data_num = data_num; + strcpy(trace_event.channel_name, channel_name); + + trace_event.computation_start_line = 0; + trace_event.computation_end_line = 0; + + add_event_node(trace_event); +} + +void functional_trace::create_write_event(const char *process_name, int data_num, const char *channel_name) +{ + //DBGPRINT; + TRACE_EVENT trace_event; + trace_event.event_type = WRITE_EVENT; + strcpy(trace_event.process_name, process_name); + trace_event.data_num = data_num; + strcpy(trace_event.channel_name, channel_name); + + trace_event.computation_start_line = 0; + trace_event.computation_end_line = 0; + + add_event_node(trace_event); +} + + +Process_Trace *functional_trace::get_process_trace(const char *process_name) +{ + Process_Trace *process_trace_ptr; + + for (_iter_process_trace = _list_process_trace.begin(); + _iter_process_trace != _list_process_trace.end(); + _iter_process_trace++) + { + process_trace_ptr = *_iter_process_trace; + + if (strcmp(process_trace_ptr->get_name(), process_name) == 0) + { + break; + } + } + + if (_iter_process_trace == _list_process_trace.end()) + { + process_trace_ptr = NULL; + } + + return process_trace_ptr; +} + +void functional_trace::free_traces() +{ + Process_Trace *process_trace_ptr; + + for (_iter_process_trace = _list_process_trace.begin(); + _iter_process_trace != _list_process_trace.end(); + _iter_process_trace++) + { + process_trace_ptr = *_iter_process_trace; + delete process_trace_ptr; + } + _list_process_trace.clear(); +} + +int functional_trace::write_to_file(const char *trace_file_name) { + int ret = -1; + Process_Trace *process_trace_ptr; + FILE *trace_file_handle; + EVENT_ENTRY *event_entry_ptr; + + if ((trace_file_handle = fopen(trace_file_name, "w")) == NULL) { + ret = -1; + printf("Can not create file: %s\n", trace_file_name); + goto end1; + } + + for (_iter_process_trace = _list_process_trace.begin(); + _iter_process_trace != _list_process_trace.end(); + _iter_process_trace++) { + process_trace_ptr = *_iter_process_trace; + fprintf(trace_file_handle, "$ %s\n", process_trace_ptr->get_name()); + + event_entry_ptr = process_trace_ptr->get_head_entry(); + while (event_entry_ptr != NULL) { + if (event_entry_ptr->event_type == COMPUTATION_EVENT) { + if (event_entry_ptr->end_line!=(event_entry_ptr->start_line+1)) + fprintf(trace_file_handle, "c %d %d\n", + event_entry_ptr->start_line, + event_entry_ptr->end_line); + } + else if (event_entry_ptr->event_type == WRITE_EVENT) { + fprintf(trace_file_handle, "w %d %s\n", + event_entry_ptr->data_num, + event_entry_ptr->channel_name); + } + else if (event_entry_ptr->event_type == READ_EVENT) { + fprintf(trace_file_handle, "r %d %s\n", + event_entry_ptr->data_num, + event_entry_ptr->channel_name); + } + + event_entry_ptr = event_entry_ptr->next; + } + } + + fflush(trace_file_handle); + fseek(trace_file_handle, 0, SEEK_SET); + fclose(trace_file_handle); + +end1: + return ret; +} + +Process_Trace::Process_Trace(const char *process_name) +{ + strcpy(_process_name, process_name); + _head = NULL; + _tail = NULL; +} + +Process_Trace::~Process_Trace() +{ + EVENT_ENTRY *temp; + + while (_head != NULL) + { + temp = _head; + _head = _head->next; + if (temp->channel_name != NULL) + delete [] temp->channel_name; + delete temp; + } +} + +const char *Process_Trace::get_name() +{ + return _process_name; +} + +EVENT_ENTRY *Process_Trace::get_head_entry() +{ + return _head; +} + +int Process_Trace::add_entry(TRACE_EVENT *trace_event_ptr) +{ + int ret = 0; + EVENT_ENTRY *temp; + int channel_name_length; + + temp = new EVENT_ENTRY; + memset(temp, 0, sizeof(EVENT_ENTRY)); + + temp->event_type = trace_event_ptr->event_type; + temp->start_line = trace_event_ptr->computation_start_line; + temp->end_line = trace_event_ptr->computation_end_line; + temp->data_num = trace_event_ptr->data_num; + + channel_name_length = strlen(trace_event_ptr->channel_name); + if (channel_name_length != 0) + { + temp->channel_name = new char[channel_name_length + 1]; + strcpy(temp->channel_name, trace_event_ptr->channel_name); + } + else + { + temp->channel_name = NULL; + } + + if (_head == NULL) + { + _head = temp; + _tail = temp; + temp->next = NULL; + } + else + { + _tail->next = temp; + _tail = temp; + temp->next = NULL; + } + + return ret; +} + + diff --git a/dol/src/dol/visitor/hds/lib/functional_trace.h b/dol/src/dol/visitor/hds/lib/functional_trace.h new file mode 100644 index 0000000..f3f0a8b --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/functional_trace.h @@ -0,0 +1,57 @@ +#ifndef _FUNCTIONAL_H +#define _FUNCTIONAL_H + +#include "trace.h" + +typedef struct EVENT_ENTRY +{ + char event_type; + int start_line; + int end_line; + int data_num; + char *channel_name; + + EVENT_ENTRY *next; +}EVENT_ENTRY; + +class Process_Trace +{ +private: + char _process_name[NAME_LENGTH]; + EVENT_ENTRY *_head; + EVENT_ENTRY *_tail; + +public: + Process_Trace(const char *process_name); + ~Process_Trace(); + + const char *get_name(); + int add_entry(TRACE_EVENT *trace_event_ptr); + EVENT_ENTRY *get_head_entry(); +}; + +class functional_trace +{ +public: + char trace_file_name[NAME_LENGTH]; + unsigned long event_num; + int file_index; + + list _list_process_trace; + list::iterator _iter_process_trace; + + functional_trace(); + ~functional_trace(); + + void add_event_node(TRACE_EVENT &trace_event); + void create_computation_event(const char *process_name, int start_line, int end_line); + void create_read_event(const char *process_name, int data_num, const char *channel_name); + void create_write_event(const char *process_name, int data_num, const char *channel_name); + int write_to_file(const char *trace_file_name); + Process_Trace *get_process_trace(const char *process_name); + void free_traces(); +}; + +extern functional_trace dol_functional_trace; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/trace.h b/dol/src/dol/visitor/hds/lib/trace.h new file mode 100644 index 0000000..3d92e67 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/trace.h @@ -0,0 +1,38 @@ +#ifndef _TRACE_H +#define _TRACE_H + +#include +#include +#include "xmlParser.h" +#include + +using namespace std; + +#define NAME_LENGTH 256 +#ifdef DBG +#define DBGPRINT printf("file: %s--line: %d\n", __FILE__, __LINE__) +#else +#define DBGPRINT +#endif + +typedef enum TRACE_EVENT_TYPE +{ + COMPUTATION_EVENT, + READ_EVENT, + WRITE_EVENT, + UNKOWN +}TRACE_EVENT_TYPE; + +#define MAX_EVENT_TYPE 3 + +typedef struct TRACE_EVENT +{ + char process_name[NAME_LENGTH]; + char event_type; + int computation_start_line; + int computation_end_line; + int data_num; + char channel_name[NAME_LENGTH]; +}TRACE_EVENT; + +#endif diff --git a/dol/src/dol/visitor/hds/lib/xmlParser.cpp b/dol/src/dol/visitor/hds/lib/xmlParser.cpp new file mode 100644 index 0000000..9868566 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/xmlParser.cpp @@ -0,0 +1,2594 @@ +/** + **************************************************************************** + *

XML.c - implementation file for basic XML parser written in ANSI C++ + * for portability. It works by using recursion and a node tree for breaking + * down the elements of an XML document.

+ * + * @version V2.23 + * @author Frank Vanden Berghen + * + * NOTE: + * + * If you add "#define STRICT_PARSING", on the first line of this file + * the parser will see the following XML-stream: + * some textother text + * as an error. Otherwise, this tring will be equivalent to: + * some textother text + * + * NOTE: + * + * If you add "#define APPROXIMATE_PARSING" on the first line of this file + * the parser will see the following XML-stream: + * + * + * + * as equivalent to the following XML-stream: + * + * + * + * This can be useful for badly-formed XML-streams but prevent the use + * of the following XML-stream (problem is: tags at contiguous levels + * have the same names): + * + * + * + * + * + * + * NOTE: + * + * If you add "#define _XMLPARSER_NO_MESSAGEBOX_" on the first line of this file + * the "openFileHelper" function will always display error messages inside the + * console instead of inside a message-box-window. Message-box-windows are + * available on windows 9x/NT/2000/XP/Vista only. + * + * BSD license: + * Copyright (c) 2002, Frank Vanden Berghen + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Frank Vanden Berghen nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************** + */ +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#include "xmlParser.h" +#ifdef _XMLWINDOWS +//#ifdef _DEBUG +//#define _CRTDBG_MAP_ALLOC +//#include +//#endif +#define WIN32_LEAN_AND_MEAN +#include // to have IsTextUnicode, MultiByteToWideChar, WideCharToMultiByte to handle unicode files + // to have "MessageBoxA" to display error messages for openFilHelper +#endif + +#include +#include +#include +#include +#include + +XMLCSTR XMLNode::getVersion() { return _T("v2.23"); } +void free_XMLDLL(void *t){free(t);} + +static char strictUTF8Parsing=1, guessUnicodeChars=1, dropWhiteSpace=1; + +inline int mmin( const int t1, const int t2 ) { return t1 < t2 ? t1 : t2; } + +// You can modify the initialization of the variable "XMLClearTags" below +// to change the clearTags that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. The "") }, + { _T("") }, + { _T("
")    ,5,  _T("
") }, + { _T("")}, + { _T("") }, + { NULL ,0, NULL } +}; +ALLXMLClearTag* XMLNode::getClearTagTable() { return XMLClearTags; } + +// You can modify the initialization of the variable "XMLEntities" below +// to change the character entities that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. Additionally, the syntaxes " " and " " are recognized. +typedef struct { XMLCSTR s; int l; XMLCHAR c;} XMLCharacterEntity; +static XMLCharacterEntity XMLEntities[] = +{ + { _T("&" ), 5, _T('&' )}, + { _T("<" ), 4, _T('<' )}, + { _T(">" ), 4, _T('>' )}, + { _T("""), 6, _T('\"')}, + { _T("'"), 6, _T('\'')}, + { NULL , 0, '\0' } +}; + +// When rendering the XMLNode to a string (using the "createXMLString" function), +// you can ask for a beautiful formatting. This formatting is using the +// following indentation character: +#define INDENTCHAR _T('\t') + +// The following function parses the XML errors into a user friendly string. +// You can edit this to change the output language of the library to something else. +XMLCSTR XMLNode::getError(XMLError xerror) +{ + switch (xerror) + { + case eXMLErrorNone: return _T("No error"); + case eXMLErrorMissingEndTag: return _T("Warning: Unmatched end tag"); + case eXMLErrorEmpty: return _T("Error: No XML data"); + case eXMLErrorFirstNotStartTag: return _T("Error: First token not start tag"); + case eXMLErrorMissingTagName: return _T("Error: Missing start tag name"); + case eXMLErrorMissingEndTagName: return _T("Error: Missing end tag name"); + case eXMLErrorNoMatchingQuote: return _T("Error: Unmatched quote"); + case eXMLErrorUnmatchedEndTag: return _T("Error: Unmatched end tag"); + case eXMLErrorUnmatchedEndClearTag: return _T("Error: Unmatched clear tag end"); + case eXMLErrorUnexpectedToken: return _T("Error: Unexpected token found"); + case eXMLErrorInvalidTag: return _T("Error: Invalid tag found"); + case eXMLErrorNoElements: return _T("Error: No elements found"); + case eXMLErrorFileNotFound: return _T("Error: File not found"); + case eXMLErrorFirstTagNotFound: return _T("Error: First Tag not found"); + case eXMLErrorUnknownCharacterEntity:return _T("Error: Unknown character entity"); + case eXMLErrorCharConversionError: return _T("Error: unable to convert between UNICODE and MultiByte chars"); + case eXMLErrorCannotOpenWriteFile: return _T("Error: unable to open file for writing"); + case eXMLErrorCannotWriteFile: return _T("Error: cannot write into file"); + + case eXMLErrorBase64DataSizeIsNotMultipleOf4: return _T("Warning: Base64-string length is not a multiple of 4"); + case eXMLErrorBase64DecodeTruncatedData: return _T("Warning: Base64-string is truncated"); + case eXMLErrorBase64DecodeIllegalCharacter: return _T("Error: Base64-string contains an illegal character"); + case eXMLErrorBase64DecodeBufferTooSmall: return _T("Error: Base64 decode output buffer is too small"); + }; + return _T("Unknown"); +} + +// Here is an abstraction layer to access some common string manipulation functions. +// The abstraction layer is currently working for gcc, Microsoft Visual Studio 6.0, +// Microsoft Visual Studio .NET, CC (sun compiler) and Borland C++. +// If you plan to "port" the library to a new system/compiler, all you have to do is +// to edit the following lines. +#ifdef XML_NO_WIDE_CHAR +char myIsTextUnicode(const void *b, int len) { return FALSE; } +#else + #if defined (UNDER_CE) || !defined(WIN32) + char myIsTextUnicode(const void *b, int len) // inspired by the Wine API: RtlIsTextUnicode + { +#ifdef sun + // for SPARC processors: wchar_t* buffers must always be alligned, otherwise it's a char* buffer. + if ((((unsigned long)b)%sizeof(wchar_t))!=0) return FALSE; +#endif + const wchar_t *s=(const wchar_t*)b; + + // buffer too small: + if (len<(int)sizeof(wchar_t)) return FALSE; + + // odd length test + if (len&1) return FALSE; + + /* only checks the first 256 characters */ + len=mmin(256,len/sizeof(wchar_t)); + + // Check for the special byte order: + if (*s == 0xFFFE) return FALSE; // IS_TEXT_UNICODE_REVERSE_SIGNATURE; + if (*s == 0xFEFF) return TRUE; // IS_TEXT_UNICODE_SIGNATURE + + // checks for ASCII characters in the UNICODE stream + int i,stats=0; + for (i=0; ilen/2) return TRUE; + + // Check for UNICODE NULL chars + for (i=0; i + int _tcsnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncasecmp(c1,c2,l);} + int _tcsicmp(XMLCSTR c1, XMLCSTR c2) { return wscasecmp(c1,c2); } + #else + // for gcc + int _tcsnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncasecmp(c1,c2,l);} + int _tcsicmp(XMLCSTR c1, XMLCSTR c2) { return wcscasecmp(c1,c2); } + #endif + XMLSTR _tcsstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)wcsstr(c1,c2); } + XMLSTR _tcscpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)wcscpy(c1,c2); } + FILE *_tfopen(XMLCSTR filename,XMLCSTR mode) + { + char *filenameAscii=myWideCharToMultiByte(filename,0); + FILE *f; + if (mode[0]==_T('r')) f=fopen(filenameAscii,"rb"); + else f=fopen(filenameAscii,"wb"); + free(filenameAscii); + return f; + } + #else + FILE *_tfopen(XMLCSTR filename,XMLCSTR mode) { return fopen(filename,mode); } + int _tcslen(XMLCSTR c) { return strlen(c); } + int _tcsnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncasecmp(c1,c2,l);} + int _tcsicmp(XMLCSTR c1, XMLCSTR c2) { return strcasecmp(c1,c2); } + XMLSTR _tcsstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)strstr(c1,c2); } + XMLSTR _tcscpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)strcpy(c1,c2); } + #endif + int _strnicmp(const char *c1,const char *c2, int l) { return strncasecmp(c1,c2,l);} +#endif + +///////////////////////////////////////////////////////////////////////// +// Here start the core implementation of the XMLParser library // +///////////////////////////////////////////////////////////////////////// + +// You should normally not change anything below this point. +// For your own information, I suggest that you read the openFileHelper below: +XMLNode XMLNode::openFileHelper(XMLCSTR filename, XMLCSTR tag) +{ + // guess the value of the global parameter "strictUTF8Parsing" + // (the guess is based on the first 200 bytes of the file). + FILE *f=_tfopen(filename,_T("rb")); + if (f) + { + char bb[205]; + int l=(int)fread(bb,1,200,f); + setGlobalOptions(guessUnicodeChars,guessUTF8ParsingParameterValue(bb,l),dropWhiteSpace); + fclose(f); + } + + // parse the file + XMLResults pResults; + XMLNode xnode=XMLNode::parseFile(filename,tag,&pResults); + + // display error message (if any) + if (pResults.error != eXMLErrorNone) + { + // create message + char message[2000],*s1=(char*)"",*s3=(char*)""; XMLCSTR s2=_T(""); + if (pResults.error==eXMLErrorFirstTagNotFound) { s1=(char*)"First Tag should be '"; s2=tag; s3=(char*)"'.\n"; } + sprintf(message, +#ifdef _XMLUNICODE + "XML Parsing error inside file '%S'.\n%S\nAt line %i, column %i.\n%s%S%s" +#else + "XML Parsing error inside file '%s'.\n%s\nAt line %i, column %i.\n%s%s%s" +#endif + ,filename,XMLNode::getError(pResults.error),pResults.nLine,pResults.nColumn,s1,s2,s3); + + // display message +#if defined(WIN32) && !defined(UNDER_CE) && !defined(_XMLPARSER_NO_MESSAGEBOX_) + MessageBoxA(NULL,message,"XML Parsing error",MB_OK|MB_ICONERROR|MB_TOPMOST); +#else + printf("%s",message); +#endif + exit(255); + } + return xnode; +} + +#ifndef _XMLUNICODE +// If "strictUTF8Parsing=0" then we assume that all characters have the same length of 1 byte. +// If "strictUTF8Parsing=1" then the characters have different lengths (from 1 byte to 4 bytes). +// This table is used as lookup-table to know the length of a character (in byte) based on the +// content of the first byte of the character. +// (note: if you modify this, you must always have XML_utf8ByteTable[0]=0 ). +static const char XML_utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70End of ASCII range + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 0x80 to 0xc1 invalid + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 0xc2 to 0xdf 2 byte + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,// 0xe0 0xe0 to 0xef 3 byte + 4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; +static const char XML_asciiByteTable[256] = +{ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +}; +static const char *XML_ByteTable=(const char *)XML_utf8ByteTable; // the default is "strictUTF8Parsing=1" +#endif + +XMLError XMLNode::writeToFile(XMLCSTR filename, const char *encoding, char nFormat) const +{ + int i; + XMLSTR t=createXMLString(nFormat,&i); + FILE *f=_tfopen(filename,_T("wb")); + if (!f) return eXMLErrorCannotOpenWriteFile; +#ifdef _XMLUNICODE + unsigned char h[2]={ 0xFF, 0xFE }; + if (!fwrite(h,2,1,f)) return eXMLErrorCannotWriteFile; + if (!isDeclaration()) + { + if (!fwrite(_T("\n"),sizeof(wchar_t)*40,1,f)) + return eXMLErrorCannotWriteFile; + } +#else + if (!isDeclaration()) + { + if ((!encoding)||(XML_ByteTable==XML_utf8ByteTable)) + { + // header so that windows recognize the file as UTF-8: +// unsigned char h[3]={0xEF,0xBB,0xBF}; +// if (!fwrite(h,3,1,f)) return eXMLErrorCannotWriteFile; + if (!fwrite("\n",39,1,f)) return eXMLErrorCannotWriteFile; + } + else + if (fprintf(f,"\n",encoding)<0) return eXMLErrorCannotWriteFile; + } else + { + if (XML_ByteTable==XML_utf8ByteTable) // test if strictUTF8Parsing==1" + { +// unsigned char h[3]={0xEF,0xBB,0xBF}; if (!fwrite(h,3,1,f)) return eXMLErrorCannotWriteFile; + } + } +#endif + if (!fwrite(t,sizeof(XMLCHAR)*i,1,f)) return eXMLErrorCannotWriteFile; + if (fclose(f)!=0) return eXMLErrorCannotWriteFile; + free(t); + return eXMLErrorNone; +} + +// Duplicate a given string. +XMLSTR stringDup(XMLCSTR lpszData, int cbData) +{ + if (lpszData==NULL) return NULL; + + XMLSTR lpszNew; + if (cbData==0) cbData=(int)_tcslen(lpszData); + lpszNew = (XMLSTR)malloc((cbData+1) * sizeof(XMLCHAR)); + if (lpszNew) + { + memcpy(lpszNew, lpszData, (cbData) * sizeof(XMLCHAR)); + lpszNew[cbData] = (XMLCHAR)NULL; + } + return lpszNew; +} + +XMLNode XMLNode::emptyXMLNode; +XMLClear XMLNode::emptyXMLClear={ NULL, NULL, NULL}; +XMLAttribute XMLNode::emptyXMLAttribute={ NULL, NULL}; + +// Enumeration used to decipher what type a token is +typedef enum XMLTokenTypeTag +{ + eTokenText = 0, + eTokenQuotedText, + eTokenTagStart, /* "<" */ + eTokenTagEnd, /* "" */ + eTokenEquals, /* "=" */ + eTokenDeclaration, /* "" */ + eTokenClear, + eTokenError +} XMLTokenType; + +// Main structure used for parsing XML +typedef struct XML +{ + XMLCSTR lpXML; + XMLCSTR lpszText; + int nIndex,nIndexMissigEndTag; + enum XMLError error; + XMLCSTR lpEndTag; + int cbEndTag; + XMLCSTR lpNewElement; + int cbNewElement; + int nFirst; +} XML; + +typedef struct +{ + ALLXMLClearTag *pClr; + XMLCSTR pStr; +} NextToken; + +// Enumeration used when parsing attributes +typedef enum Attrib +{ + eAttribName = 0, + eAttribEquals, + eAttribValue +} Attrib; + +// Enumeration used when parsing elements to dictate whether we are currently +// inside a tag +typedef enum Status +{ + eInsideTag = 0, + eOutsideTag +} Status; + +// private (used while rendering): +XMLSTR toXMLString(XMLSTR dest,XMLCSTR source) +{ + XMLSTR dd=dest; + XMLCHAR ch; + XMLCharacterEntity *entity; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) {_tcscpy(dest,entity->s); dest+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLUNICODE + *(dest++)=*(source++); +#else + switch(XML_ByteTable[(unsigned char)ch]) + { + case 4: *(dest++)=*(source++); + case 3: *(dest++)=*(source++); + case 2: *(dest++)=*(source++); + case 1: *(dest++)=*(source++); + } +#endif +out_of_loop1: + ; + } + *dest=0; + return dd; +} + +// private (used while rendering): +int lengthXMLString(XMLCSTR source) +{ + int r=0; + XMLCharacterEntity *entity; + XMLCHAR ch; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) { r+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLUNICODE + r++; source++; +#else + ch=XML_ByteTable[(unsigned char)ch]; r+=ch; source+=ch; +#endif +out_of_loop1: + ; + } + return r; +} + +XMLSTR toXMLString(XMLCSTR source) +{ + XMLSTR dest=(XMLSTR)malloc((lengthXMLString(source)+1)*sizeof(XMLCHAR)); + return toXMLString(dest,source); +} + +XMLSTR toXMLStringFast(XMLSTR *dest,int *destSz, XMLCSTR source) +{ + int l=lengthXMLString(source)+1; + if (l>*destSz) { *destSz=l; *dest=(XMLSTR)realloc(*dest,l*sizeof(XMLCHAR)); } + return toXMLString(*dest,source); +} + +// private: +XMLSTR fromXMLString(XMLCSTR s, int lo, XML *pXML) +{ + // This function is the opposite of the function "toXMLString". It decodes the escape + // sequences &, ", ', <, > and replace them by the characters + // &,",',<,>. This function is used internally by the XML Parser. All the calls to + // the XML library will always gives you back "decoded" strings. + // + // in: string (s) and length (lo) of string + // out: new allocated string converted from xml + if (!s) return NULL; + + int ll=0,j; + XMLSTR d; + XMLCSTR ss=s; + XMLCharacterEntity *entity; + while ((lo>0)&&(*s)) + { + if (*s==_T('&')) + { + if ((lo>2)&&(s[1]==_T('#'))) + { + s+=2; lo-=2; + if ((*s==_T('X'))||(*s==_T('x'))) { s++; lo--; } + while ((*s)&&(*s!=_T(';'))&&((lo--)>0)) s++; + if (*s!=_T(';')) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + s++; lo--; + } else + { + entity=XMLEntities; + do + { + if ((lo>=entity->l)&&(_tcsnicmp(s,entity->s,entity->l)==0)) { s+=entity->l; lo-=entity->l; break; } + entity++; + } while(entity->s); + if (!entity->s) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + } + } else + { +#ifdef _XMLUNICODE + s++; lo--; +#else + j=XML_ByteTable[(unsigned char)*s]; s+=j; lo-=j; ll+=j-1; +#endif + } + ll++; + } + + d=(XMLSTR)malloc((ll+1)*sizeof(XMLCHAR)); + s=d; + while (ll-->0) + { + if (*ss==_T('&')) + { + if (ss[1]==_T('#')) + { + ss+=2; j=0; + if ((*ss==_T('X'))||(*ss==_T('x'))) + { + ss++; + while (*ss!=_T(';')) + { + if ((*ss>=_T('0'))&&(*ss<=_T('9'))) j=(j<<4)+*ss-_T('0'); + else if ((*ss>=_T('A'))&&(*ss<=_T('F'))) j=(j<<4)+*ss-_T('A')+10; + else if ((*ss>=_T('a'))&&(*ss<=_T('f'))) j=(j<<4)+*ss-_T('a')+10; + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } else + { + while (*ss!=_T(';')) + { + if ((*ss>=_T('0'))&&(*ss<=_T('9'))) j=(j*10)+*ss-_T('0'); + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } + (*d++)=(XMLCHAR)j; ss++; + } else + { + entity=XMLEntities; + do + { + if (_tcsnicmp(ss,entity->s,entity->l)==0) { *(d++)=entity->c; ss+=entity->l; break; } + entity++; + } while(entity->s); + } + } else + { +#ifdef _XMLUNICODE + *(d++)=*(ss++); +#else + switch(XML_ByteTable[(unsigned char)*ss]) + { + case 4: *(d++)=*(ss++); ll--; + case 3: *(d++)=*(ss++); ll--; + case 2: *(d++)=*(ss++); ll--; + case 1: *(d++)=*(ss++); + } +#endif + } + } + *d=0; + return (XMLSTR)s; +} + +#define XML_isSPACECHAR(ch) ((ch==_T('\n'))||(ch==_T(' '))||(ch== _T('\t'))||(ch==_T('\r'))) + +// private: +char myTagCompare(XMLCSTR cclose, XMLCSTR copen) +// !!!! WARNING strange convention&: +// return 0 if equals +// return 1 if different +{ + if (!cclose) return 1; + int l=(int)_tcslen(cclose); + if (_tcsnicmp(cclose, copen, l)!=0) return 1; + const XMLCHAR c=copen[l]; + if (XML_isSPACECHAR(c)|| + (c==_T('/' ))|| + (c==_T('<' ))|| + (c==_T('>' ))|| + (c==_T('=' ))) return 0; + return 1; +} + +// Obtain the next character from the string. +static inline XMLCHAR getNextChar(XML *pXML) +{ + XMLCHAR ch = pXML->lpXML[pXML->nIndex]; +#ifdef _XMLUNICODE + if (ch!=0) pXML->nIndex++; +#else + pXML->nIndex+=XML_ByteTable[(unsigned char)ch]; +#endif + return ch; +} + +// Find the next token in a string. +// pcbToken contains the number of characters that have been read. +static NextToken GetNextToken(XML *pXML, int *pcbToken, enum XMLTokenTypeTag *pType) +{ + NextToken result; + XMLCHAR ch; + XMLCHAR chTemp; + int indexStart,nFoundMatch,nIsText=FALSE; + result.pClr=NULL; // prevent warning + + // Find next non-white space character + do { indexStart=pXML->nIndex; ch=getNextChar(pXML); } while XML_isSPACECHAR(ch); + + if (ch) + { + // Cache the current string pointer + result.pStr = &pXML->lpXML[indexStart]; + + // First check whether the token is in the clear tag list (meaning it + // does not need formatting). + ALLXMLClearTag *ctag=XMLClearTags; + do + { + if (_tcsnicmp(ctag->lpszOpen, result.pStr, ctag->openTagLen)==0) + { + result.pClr=ctag; + pXML->nIndex+=ctag->openTagLen-1; + *pType=eTokenClear; + return result; + } + ctag++; + } while(ctag->lpszOpen); + + // If we didn't find a clear tag then check for standard tokens + switch(ch) + { + // Check for quotes + case _T('\''): + case _T('\"'): + // Type of token + *pType = eTokenQuotedText; + chTemp = ch; + + // Set the size + nFoundMatch = FALSE; + + // Search through the string to find a matching quote + while((ch = getNextChar(pXML))) + { + if (ch==chTemp) { nFoundMatch = TRUE; break; } + if (ch==_T('<')) break; + } + + // If we failed to find a matching quote + if (nFoundMatch == FALSE) + { + pXML->nIndex=indexStart+1; + nIsText=TRUE; + break; + } + +// 4.02.2002 +// if (FindNonWhiteSpace(pXML)) pXML->nIndex--; + + break; + + // Equals (used with attribute values) + case _T('='): + *pType = eTokenEquals; + break; + + // Close tag + case _T('>'): + *pType = eTokenCloseTag; + break; + + // Check for tag start and tag end + case _T('<'): + + // Peek at the next character to see if we have an end tag 'lpXML[pXML->nIndex]; + + // If we have a tag end... + if (chTemp == _T('/')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenTagEnd; + } + + // If we have an XML declaration tag + else if (chTemp == _T('?')) + { + + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenDeclaration; + } + + // Otherwise we must have a start tag + else + { + *pType = eTokenTagStart; + } + break; + + // Check to see if we have a short hand type end tag ('/>'). + case _T('/'): + + // Peek at the next character to see if we have a short end tag '/>' + chTemp = pXML->lpXML[pXML->nIndex]; + + // If we have a short hand end tag... + if (chTemp == _T('>')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenShortHandClose; + break; + } + + // If we haven't found a short hand closing tag then drop into the + // text process + + // Other characters + default: + nIsText = TRUE; + } + + // If this is a TEXT node + if (nIsText) + { + // Indicate we are dealing with text + *pType = eTokenText; + while((ch = getNextChar(pXML))) + { + if XML_isSPACECHAR(ch) + { + indexStart++; break; + + } else if (ch==_T('/')) + { + // If we find a slash then this maybe text or a short hand end tag + // Peek at the next character to see it we have short hand end tag + ch=pXML->lpXML[pXML->nIndex]; + // If we found a short hand end tag then we need to exit the loop + if (ch==_T('>')) { pXML->nIndex--; break; } + + } else if ((ch==_T('<'))||(ch==_T('>'))||(ch==_T('='))) + { + pXML->nIndex--; break; + } + } + } + *pcbToken = pXML->nIndex-indexStart; + } else + { + // If we failed to obtain a valid character + *pcbToken = 0; + *pType = eTokenError; + result.pStr=NULL; + } + + return result; +} + +XMLCSTR XMLNode::updateName_WOSD(XMLCSTR lpszName) +{ + if (d->lpszName&&(lpszName!=d->lpszName)) free((void*)d->lpszName); + d->lpszName=lpszName; + return lpszName; +} + +// private: +XMLNode::XMLNode(struct XMLNodeDataTag *p){ d=p; (p->ref_count)++; } +XMLNode::XMLNode(XMLNodeData *pParent, XMLCSTR lpszName, char isDeclaration) +{ + d=(XMLNodeData*)malloc(sizeof(XMLNodeData)); + d->ref_count=1; + + d->lpszName=NULL; + d->nChild= 0; + d->nText = 0; + d->nClear = 0; + d->nAttribute = 0; + + d->isDeclaration = isDeclaration; + + d->pParent = pParent; + d->pChild= NULL; + d->pText= NULL; + d->pClear= NULL; + d->pAttribute= NULL; + d->pOrder= NULL; + + updateName_WOSD(lpszName); +} + +XMLNode XMLNode::createXMLTopNode_WOSD(XMLCSTR lpszName, char isDeclaration) { return XMLNode(NULL,lpszName,isDeclaration); } +XMLNode XMLNode::createXMLTopNode(XMLCSTR lpszName, char isDeclaration) { return XMLNode(NULL,stringDup(lpszName),isDeclaration); } + +#define MEMORYINCREASE 50 + +static inline void *myRealloc(void *p, int newsize, int memInc, int sizeofElem) +{ + if (p==NULL) { if (memInc) return malloc(memInc*sizeofElem); return malloc(sizeofElem); } + if ((memInc==0)||((newsize%memInc)==0)) p=realloc(p,(newsize+memInc)*sizeofElem); +// if (!p) +// { +// printf("XMLParser Error: Not enough memory! Aborting...\n"); exit(220); +// } + return p; +} + +// private: +int XMLNode::findPosition(XMLNodeData *d, int index, XMLElementType xtype) +{ + if (index<0) return -1; + int i=0,j=(int)((index<<2)+xtype),*o=d->pOrder; while (o[i]!=j) i++; return i; +} + +// private: +// update "order" information when deleting a content of a XMLNode +int XMLNode::removeOrderElement(XMLNodeData *d, XMLElementType t, int index) +{ + int n=d->nChild+d->nText+d->nClear, *o=d->pOrder,i=findPosition(d,index,t); + memmove(o+i, o+i+1, (n-i)*sizeof(int)); + for (;ipOrder=(int)realloc(d->pOrder,n*sizeof(int)); + // but we skip reallocation because it's too time consuming. + // Anyway, at the end, it will be free'd completely at once. + return i; +} + +void *XMLNode::addToOrder(int memoryIncrease,int *_pos, int nc, void *p, int size, XMLElementType xtype) +{ + // in: *_pos is the position inside d->pOrder ("-1" means "EndOf") + // out: *_pos is the index inside p + p=myRealloc(p,(nc+1),memoryIncrease,size); + int n=d->nChild+d->nText+d->nClear; + d->pOrder=(int*)myRealloc(d->pOrder,n+1,memoryIncrease*3,sizeof(int)); + int pos=*_pos,*o=d->pOrder; + + if ((pos<0)||(pos>=n)) { *_pos=nc; o[n]=(int)((nc<<2)+xtype); return p; } + + int i=pos; + memmove(o+i+1, o+i, (n-i)*sizeof(int)); + + while ((pos>2; + memmove(((char*)p)+(pos+1)*size,((char*)p)+pos*size,(nc-pos)*size); + + return p; +} + +// Add a child node to the given element. +XMLNode XMLNode::addChild_priv(int memoryIncrease, XMLCSTR lpszName, char isDeclaration, int pos) +{ + if (!lpszName) return emptyXMLNode; + d->pChild=(XMLNode*)addToOrder(memoryIncrease,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=NULL; + d->pChild[pos]=XMLNode(d,lpszName,isDeclaration); + d->nChild++; + return d->pChild[pos]; +} + +// Add an attribute to an element. +XMLAttribute *XMLNode::addAttribute_priv(int memoryIncrease,XMLCSTR lpszName, XMLCSTR lpszValuev) +{ + if (!lpszName) return &emptyXMLAttribute; + int nc=d->nAttribute; + d->pAttribute=(XMLAttribute*)myRealloc(d->pAttribute,(nc+1),memoryIncrease,sizeof(XMLAttribute)); + XMLAttribute *pAttr=d->pAttribute+nc; + pAttr->lpszName = lpszName; + pAttr->lpszValue = lpszValuev; + d->nAttribute++; + return pAttr; +} + +// Add text to the element. +XMLCSTR XMLNode::addText_priv(int memoryIncrease, XMLCSTR lpszValue, int pos) +{ + if (!lpszValue) return NULL; + d->pText=(XMLCSTR*)addToOrder(memoryIncrease,&pos,d->nText,d->pText,sizeof(XMLSTR),eNodeText); + d->pText[pos]=lpszValue; + d->nText++; + return lpszValue; +} + +// Add clear (unformatted) text to the element. +XMLClear *XMLNode::addClear_priv(int memoryIncrease, XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) +{ + if (!lpszValue) return &emptyXMLClear; + d->pClear=(XMLClear *)addToOrder(memoryIncrease,&pos,d->nClear,d->pClear,sizeof(XMLClear),eNodeClear); + XMLClear *pNewClear=d->pClear+pos; + pNewClear->lpszValue = lpszValue; + if (!lpszOpen) lpszOpen=getClearTagTable()->lpszOpen; + if (!lpszClose) lpszOpen=getClearTagTable()->lpszClose; + pNewClear->lpszOpenTag = lpszOpen; + pNewClear->lpszCloseTag = lpszClose; + d->nClear++; + return pNewClear; +} + +// private: +// Parse a clear (unformatted) type node. +char XMLNode::parseClearTag(void *px, ALLXMLClearTag *pClear) +{ + XML *pXML=(XML *)px; + int cbTemp=0; + XMLCSTR lpszTemp=NULL; + XMLCSTR lpXML=&pXML->lpXML[pXML->nIndex]; + static XMLCSTR docTypeEnd=_T("]>"); + + // Find the closing tag + // Seems the lpszOpen==XMLClearTags[1].lpszOpen) + { + XMLCSTR pCh=lpXML; + while (*pCh) + { + if (*pCh==_T('<')) { pClear->lpszClose=docTypeEnd; lpszTemp=_tcsstr(lpXML,docTypeEnd); break; } + else if (*pCh==_T('>')) { lpszTemp=pCh; break; } +#ifdef _XMLUNICODE + pCh++; +#else + pCh+=XML_ByteTable[(unsigned char)(*pCh)]; +#endif + } + } else lpszTemp=_tcsstr(lpXML, pClear->lpszClose); + + if (lpszTemp) + { + // Cache the size and increment the index + cbTemp = (int)(lpszTemp - lpXML); + + pXML->nIndex += cbTemp+(int)_tcslen(pClear->lpszClose); + + // Add the clear node to the current element + addClear_priv(MEMORYINCREASE,stringDup(lpXML,cbTemp), pClear->lpszOpen, pClear->lpszClose,-1); + return 0; + } + + // If we failed to find the end tag + pXML->error = eXMLErrorUnmatchedEndClearTag; + return 1; +} + +void XMLNode::exactMemory(XMLNodeData *d) +{ + if (d->pOrder) d->pOrder=(int*)realloc(d->pOrder,(d->nChild+d->nText+d->nClear)*sizeof(int)); + if (d->pChild) d->pChild=(XMLNode*)realloc(d->pChild,d->nChild*sizeof(XMLNode)); + if (d->pAttribute) d->pAttribute=(XMLAttribute*)realloc(d->pAttribute,d->nAttribute*sizeof(XMLAttribute)); + if (d->pText) d->pText=(XMLCSTR*)realloc(d->pText,d->nText*sizeof(XMLSTR)); + if (d->pClear) d->pClear=(XMLClear *)realloc(d->pClear,d->nClear*sizeof(XMLClear)); +} + +char XMLNode::maybeAddTxT(void *pa, XMLCSTR tokenPStr) +{ + XML *pXML=(XML *)pa; + XMLCSTR lpszText=pXML->lpszText; + if (!lpszText) return 0; + if (dropWhiteSpace) while (XML_isSPACECHAR(*lpszText)&&(lpszText!=tokenPStr)) lpszText++; + int cbText = (int)(tokenPStr - lpszText); + if (!cbText) { pXML->lpszText=NULL; return 0; } + if (dropWhiteSpace) { cbText--; while ((cbText)&&XML_isSPACECHAR(lpszText[cbText])) cbText--; cbText++; } + if (!cbText) { pXML->lpszText=NULL; return 0; } + lpszText=fromXMLString(lpszText,cbText,pXML); + if (!lpszText) return 1; + addText_priv(MEMORYINCREASE,lpszText,-1); + pXML->lpszText=NULL; + return 0; +} +// private: +// Recursively parse an XML element. +int XMLNode::ParseXMLElement(void *pa) +{ + XML *pXML=(XML *)pa; + int cbToken; + enum XMLTokenTypeTag type; + NextToken token; + XMLCSTR lpszTemp=NULL; + int cbTemp=0; + char nDeclaration; + XMLNode pNew; + enum Status status; // inside or outside a tag + enum Attrib attrib = eAttribName; + + assert(pXML); + + // If this is the first call to the function + if (pXML->nFirst) + { + // Assume we are outside of a tag definition + pXML->nFirst = FALSE; + status = eOutsideTag; + } else + { + // If this is not the first call then we should only be called when inside a tag. + status = eInsideTag; + } + + // Iterate through the tokens in the document + for(;;) + { + // Obtain the next token + token = GetNextToken(pXML, &cbToken, &type); + + if (type != eTokenError) + { + // Check the current status + switch(status) + { + + // If we are outside of a tag definition + case eOutsideTag: + + // Check what type of token we obtained + switch(type) + { + // If we have found text or quoted text + case eTokenText: + case eTokenCloseTag: /* '>' */ + case eTokenShortHandClose: /* '/>' */ + case eTokenQuotedText: + case eTokenEquals: + break; + + // If we found a start tag '<' and declarations 'error = eXMLErrorMissingTagName; + return FALSE; + } + + // If we found a new element which is the same as this + // element then we need to pass this back to the caller.. + +#ifdef APPROXIMATE_PARSING + if (d->lpszName && + myTagCompare(d->lpszName, token.pStr) == 0) + { + // Indicate to the caller that it needs to create a + // new element. + pXML->lpNewElement = token.pStr; + pXML->cbNewElement = cbToken; + return TRUE; + } else +#endif + { + // If the name of the new element differs from the name of + // the current element we need to add the new element to + // the current one and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(token.pStr,cbToken), nDeclaration,-1); + + while (!pNew.isEmpty()) + { + // Callself to process the new node. If we return + // FALSE this means we dont have any more + // processing to do... + + if (!pNew.ParseXMLElement(pXML)) return FALSE; + else + { + // If the call to recurse this function + // evented in a end tag specified in XML then + // we need to unwind the calls to this + // function until we find the appropriate node + // (the element name and end tag name must + // match) + if (pXML->cbEndTag) + { + // If we are back at the root node then we + // have an unmatched end tag + if (!d->lpszName) + { + pXML->error=eXMLErrorUnmatchedEndTag; + return FALSE; + } + + // If the end tag matches the name of this + // element then we only need to unwind + // once more... + + if (myTagCompare(d->lpszName, pXML->lpEndTag)==0) + { + pXML->cbEndTag = 0; + } + + return TRUE; + } else + if (pXML->cbNewElement) + { + // If the call indicated a new element is to + // be created on THIS element. + + // If the name of this element matches the + // name of the element we need to create + // then we need to return to the caller + // and let it process the element. + + if (myTagCompare(d->lpszName, pXML->lpNewElement)==0) + { + return TRUE; + } + + // Add the new element and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(pXML->lpNewElement,pXML->cbNewElement),0,-1); + pXML->cbNewElement = 0; + } + else + { + // If we didn't have a new element to create + pNew = emptyXMLNode; + + } + } + } + } + break; + + // If we found an end tag + case eTokenTagEnd: + + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + + // Find the name of the end tag + token = GetNextToken(pXML, &cbTemp, &type); + + // The end tag should be text + if (type != eTokenText) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + lpszTemp = token.pStr; + + // After the end tag we should find a closing tag + token = GetNextToken(pXML, &cbToken, &type); + if (type != eTokenCloseTag) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + pXML->lpszText=pXML->lpXML+pXML->nIndex; + + // We need to return to the previous caller. If the name + // of the tag cannot be found we need to keep returning to + // caller until we find a match + if (myTagCompare(d->lpszName, lpszTemp) != 0) +#ifdef STRICT_PARSING + { + pXML->error=eXMLErrorUnmatchedEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + return FALSE; + } +#else + { + pXML->error=eXMLErrorMissingEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + pXML->lpEndTag = lpszTemp; + pXML->cbEndTag = cbTemp; + } +#endif + + // Return to the caller + exactMemory(d); + return TRUE; + + // If we found a clear (unformatted) token + case eTokenClear: + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + if (parseClearTag(pXML, token.pClr)) return FALSE; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + default: + break; + } + break; + + // If we are inside a tag definition we need to search for attributes + case eInsideTag: + + // Check what part of the attribute (name, equals, value) we + // are looking for. + switch(attrib) + { + // If we are looking for a new attribute + case eAttribName: + + // Check what the current token type is + switch(type) + { + // If the current type is text... + // Eg. 'attribute' + case eTokenText: + // Cache the token then indicate that we are next to + // look for the equals + lpszTemp = token.pStr; + cbTemp = cbToken; + attrib = eAttribEquals; + break; + + // If we found a closing tag... + // Eg. '>' + case eTokenCloseTag: + // We are now outside the tag + status = eOutsideTag; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + // If we found a short hand '/>' closing tag then we can + // return to the caller + case eTokenShortHandClose: + exactMemory(d); + pXML->lpszText=pXML->lpXML+pXML->nIndex; + return TRUE; + + // Errors... + case eTokenQuotedText: /* '"SomeText"' */ + case eTokenTagStart: /* '<' */ + case eTokenTagEnd: /* 'error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an equals + case eAttribEquals: + // Check what the current token type is + switch(type) + { + // If the current type is text... + // Eg. 'Attribute AnotherAttribute' + case eTokenText: + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + // Cache the token then indicate. We are next to + // look for the equals attribute + lpszTemp = token.pStr; + cbTemp = cbToken; + break; + + // If we found a closing tag 'Attribute >' or a short hand + // closing tag 'Attribute />' + case eTokenShortHandClose: + case eTokenCloseTag: + // If we are a declaration element 'lpszText=pXML->lpXML+pXML->nIndex; + + if (d->isDeclaration && + (lpszTemp[cbTemp-1]) == _T('?')) + { + cbTemp--; + } + + if (cbTemp) + { + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + } + + // If this is the end of the tag then return to the caller + if (type == eTokenShortHandClose) + { + exactMemory(d); + return TRUE; + } + + // We are now outside the tag + status = eOutsideTag; + break; + + // If we found the equals token... + // Eg. 'Attribute =' + case eTokenEquals: + // Indicate that we next need to search for the value + // for the attribute + attrib = eAttribValue; + break; + + // Errors... + case eTokenQuotedText: /* 'Attribute "InvalidAttr"'*/ + case eTokenTagStart: /* 'Attribute <' */ + case eTokenTagEnd: /* 'Attribute error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an attribute value + case eAttribValue: + // Check what the current token type is + switch(type) + { + // If the current type is text or quoted text... + // Eg. 'Attribute = "Value"' or 'Attribute = Value' or + // 'Attribute = 'Value''. + case eTokenText: + case eTokenQuotedText: + // If we are a declaration element 'isDeclaration && + (token.pStr[cbToken-1]) == _T('?')) + { + cbToken--; + } + + if (cbTemp) + { + // Add the valued attribute to the list + if (type==eTokenQuotedText) { token.pStr++; cbToken-=2; } + XMLCSTR attrVal=token.pStr; + if (attrVal) + { + attrVal=fromXMLString(attrVal,cbToken,pXML); + if (!attrVal) return FALSE; + } + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp),attrVal); + } + + // Indicate we are searching for a new attribute + attrib = eAttribName; + break; + + // Errors... + case eTokenTagStart: /* 'Attr = <' */ + case eTokenTagEnd: /* 'Attr = ' */ + case eTokenShortHandClose: /* "Attr = />" */ + case eTokenEquals: /* 'Attr = =' */ + case eTokenDeclaration: /* 'Attr = error = eXMLErrorUnexpectedToken; + return FALSE; + break; + default: break; + } + } + } + } + // If we failed to obtain the next token + else + { + if ((!d->isDeclaration)&&(d->pParent)) + { +#ifdef STRICT_PARSING + pXML->error=eXMLErrorUnmatchedEndTag; +#else + pXML->error=eXMLErrorMissingEndTag; +#endif + pXML->nIndexMissigEndTag=pXML->nIndex; + } + return FALSE; + } + } +} + +// Count the number of lines and columns in an XML string. +static void CountLinesAndColumns(XMLCSTR lpXML, int nUpto, XMLResults *pResults) +{ + XMLCHAR ch; + assert(lpXML); + assert(pResults); + + struct XML xml={ lpXML,lpXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + pResults->nLine = 1; + pResults->nColumn = 1; + while (xml.nIndexnColumn++; + else + { + pResults->nLine++; + pResults->nColumn=1; + } + } +} + +// Parse XML and return the root element. +XMLNode XMLNode::parseString(XMLCSTR lpszXML, XMLCSTR tag, XMLResults *pResults) +{ + if (!lpszXML) + { + if (pResults) + { + pResults->error=eXMLErrorNoElements; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + + XMLNode xnode(NULL,NULL,FALSE); + struct XML xml={ lpszXML, lpszXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + // Create header element + xnode.ParseXMLElement(&xml); + enum XMLError error = xml.error; + if ((xnode.nChildNode()==1)&&(xnode.nElement()==1)) xnode=xnode.getChildNode(); // skip the empty node + + // If no error occurred + if ((error==eXMLErrorNone)||(error==eXMLErrorMissingEndTag)) + { + XMLCSTR name=xnode.getName(); + if (tag&&_tcslen(tag)&&((!name)||(_tcsicmp(xnode.getName(),tag)))) + { + XMLNode nodeTmp; + int i=0; + while (i=xnode.nChildNode()) + { + if (pResults) + { + pResults->error=eXMLErrorFirstTagNotFound; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + xnode=nodeTmp; + } + } else + { + // Cleanup: this will destroy all the nodes + xnode = emptyXMLNode; + } + + + // If we have been given somewhere to place results + if (pResults) + { + pResults->error = error; + + // If we have an error + if (error!=eXMLErrorNone) + { + if (error==eXMLErrorMissingEndTag) xml.nIndex=xml.nIndexMissigEndTag; + // Find which line and column it starts on. + CountLinesAndColumns(xml.lpXML, xml.nIndex, pResults); + } + } + return xnode; +} + +XMLNode XMLNode::parseFile(XMLCSTR filename, XMLCSTR tag, XMLResults *pResults) +{ + if (pResults) { pResults->nLine=0; pResults->nColumn=0; } + FILE *f=_tfopen(filename,_T("rb")); + if (f==NULL) { if (pResults) pResults->error=eXMLErrorFileNotFound; return emptyXMLNode; } + fseek(f,0,SEEK_END); + int l=ftell(f),headerSz=0; + if (!l) { if (pResults) pResults->error=eXMLErrorEmpty; return emptyXMLNode; } + fseek(f,0,SEEK_SET); + unsigned char *buf=(unsigned char*)malloc(l+1); + fread(buf,l,1,f); + fclose(f); + buf[l]=0; +#ifdef _XMLUNICODE + if (guessUnicodeChars) + { + if (!myIsTextUnicode(buf,l)) + { + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + XMLSTR b2=myMultiByteToWideChar((const char*)(buf+headerSz),l-headerSz); + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + } + } +#else + if (guessUnicodeChars) + { + if (myIsTextUnicode(buf,l)) + { + l/=sizeof(wchar_t); + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + char *b2=myWideCharToMultiByte((const wchar_t*)(buf+headerSz),l-headerSz); + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } + } +#endif + + if (!buf) { if (pResults) pResults->error=eXMLErrorCharConversionError; return emptyXMLNode; } + XMLNode x=parseString((XMLSTR)(buf+headerSz),tag,pResults); + free(buf); + return x; +} + +static inline void charmemset(XMLSTR dest,XMLCHAR c,int l) { while (l--) *(dest++)=c; } +// private: +// Creates an user friendly XML string from a given element with +// appropriate white space and carriage returns. +// +// This recurses through all subnodes then adds contents of the nodes to the +// string. +int XMLNode::CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat) +{ + int nResult = 0; + int cb; + int cbElement; + int nChildFormat=-1; + int nElementI=pEntry->nChild+pEntry->nText+pEntry->nClear; + int i,j; + + assert(pEntry); + +#define LENSTR(lpsz) (lpsz ? _tcslen(lpsz) : 0) + + // If the element has no name then assume this is the head node. + cbElement = (int)LENSTR(pEntry->lpszName); + + if (cbElement) + { + // "isDeclaration) lpszMarker[nResult++]=_T('?'); + _tcscpy(&lpszMarker[nResult], pEntry->lpszName); + nResult+=cbElement; + lpszMarker[nResult++]=_T(' '); + + } else + { + nResult+=cbElement+2+cb; + if (pEntry->isDeclaration) nResult++; + } + + // Enumerate attributes and add them to the string + XMLAttribute *pAttr=pEntry->pAttribute; + for (i=0; inAttribute; i++) + { + // "Attrib + cb = (int)LENSTR(pAttr->lpszName); + if (cb) + { + if (lpszMarker) _tcscpy(&lpszMarker[nResult], pAttr->lpszName); + nResult += cb; + // "Attrib=Value " + if (pAttr->lpszValue) + { + cb=(int)lengthXMLString(pAttr->lpszValue); + if (lpszMarker) + { + lpszMarker[nResult]=_T('='); + lpszMarker[nResult+1]=_T('"'); + if (cb) toXMLString(&lpszMarker[nResult+2],pAttr->lpszValue); + lpszMarker[nResult+cb+2]=_T('"'); + } + nResult+=cb+3; + } + if (lpszMarker) lpszMarker[nResult] = _T(' '); + nResult++; + } + pAttr++; + } + + if (pEntry->isDeclaration) + { + if (lpszMarker) + { + lpszMarker[nResult-1]=_T('?'); + lpszMarker[nResult]=_T('>'); + } + nResult++; + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult]=_T('\n'); + nResult++; + } + } else + // If there are child nodes we need to terminate the start tag + if (nElementI) + { + if (lpszMarker) lpszMarker[nResult-1]=_T('>'); + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult]=_T('\n'); + nResult++; + } + } else nResult--; + } + + // Calculate the child format for when we recurse. This is used to + // determine the number of spaces used for prefixes. + if (nFormat!=-1) + { + if (cbElement&&(!pEntry->isDeclaration)) nChildFormat=nFormat+1; + else nChildFormat=nFormat; + } + + // Enumerate through remaining children + for (i=0; ipOrder[i]; + switch((XMLElementType)(j&3)) + { + // Text nodes + case eNodeText: + { + // "Text" + XMLCSTR pChild=pEntry->pText[j>>2]; + cb = (int)lengthXMLString(pChild); + if (cb) + { + if (nFormat!=-1) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult],INDENTCHAR,sizeof(XMLCHAR)*(nFormat + 1)); + toXMLString(&lpszMarker[nResult+nFormat+1],pChild); + lpszMarker[nResult+nFormat+1+cb]=_T('\n'); + } + nResult+=cb+nFormat+2; + } else + { + if (lpszMarker) toXMLString(&lpszMarker[nResult], pChild); + nResult += cb; + } + } + break; + } + + // Clear type nodes + case eNodeClear: + { + XMLClear *pChild=pEntry->pClear+(j>>2); + // "OpenTag" + cb = (int)LENSTR(pChild->lpszOpenTag); + if (cb) + { + if (nFormat!=-1) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult], INDENTCHAR, sizeof(XMLCHAR)*(nFormat + 1)); + _tcscpy(&lpszMarker[nResult+nFormat+1], pChild->lpszOpenTag); + } + nResult+=cb+nFormat+1; + } + else + { + if (lpszMarker)_tcscpy(&lpszMarker[nResult], pChild->lpszOpenTag); + nResult += cb; + } + } + + // "OpenTag Value" + cb = (int)LENSTR(pChild->lpszValue); + if (cb) + { + if (lpszMarker) _tcscpy(&lpszMarker[nResult], pChild->lpszValue); + nResult += cb; + } + + // "OpenTag Value CloseTag" + cb = (int)LENSTR(pChild->lpszCloseTag); + if (cb) + { + if (lpszMarker) _tcscpy(&lpszMarker[nResult], pChild->lpszCloseTag); + nResult += cb; + } + + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult] = _T('\n'); + nResult++; + } + break; + } + + // Element nodes + case eNodeChild: + { + // Recursively add child nodes + nResult += CreateXMLStringR(pEntry->pChild[j>>2].d, lpszMarker ? lpszMarker + nResult : 0, nChildFormat); + break; + } + default: break; + } + } + + if ((cbElement)&&(!pEntry->isDeclaration)) + { + // If we have child entries we need to use long XML notation for + // closing the element - "blah blah blah" + if (nElementI) + { + // "\0" + if (lpszMarker) + { + if (nFormat != -1) + { + if (nFormat) + { + charmemset(&lpszMarker[nResult], INDENTCHAR,sizeof(XMLCHAR)*nFormat); + nResult+=nFormat; + } + } + + _tcscpy(&lpszMarker[nResult], _T("lpszName); + nResult += cbElement; + + if (nFormat == -1) + { + _tcscpy(&lpszMarker[nResult], _T(">")); + nResult++; + } else + { + _tcscpy(&lpszMarker[nResult], _T(">\n")); + nResult+=2; + } + } else + { + if (nFormat != -1) nResult+=cbElement+4+nFormat; + else nResult+=cbElement+3; + } + } else + { + // If there are no children we can use shorthand XML notation - + // "" + // "/>\0" + if (lpszMarker) + { + if (nFormat == -1) + { + _tcscpy(&lpszMarker[nResult], _T("/>")); + nResult += 2; + } + else + { + _tcscpy(&lpszMarker[nResult], _T("/>\n")); + nResult += 3; + } + } + else + { + nResult += nFormat == -1 ? 2 : 3; + } + } + } + + return nResult; +} + +#undef LENSTR + +// Create an XML string +// @param int nFormat - 0 if no formatting is required +// otherwise nonzero for formatted text +// with carriage returns and indentation. +// @param int *pnSize - [out] pointer to the size of the +// returned string not including the +// NULL terminator. +// @return XMLSTR - Allocated XML string, you must free +// this with free(). +XMLSTR XMLNode::createXMLString(int nFormat, int *pnSize) const +{ + if (!d) { if (pnSize) *pnSize=0; return NULL; } + + XMLSTR lpszResult = NULL; + int cbStr; + + // Recursively Calculate the size of the XML string + if (!dropWhiteSpace) nFormat=0; + nFormat = nFormat ? 0 : -1; + cbStr = CreateXMLStringR(d, 0, nFormat); + assert(cbStr); + // Alllocate memory for the XML string + the NULL terminator and + // create the recursively XML string. + lpszResult=(XMLSTR)malloc((cbStr+1)*sizeof(XMLCHAR)); + CreateXMLStringR(d, lpszResult, nFormat); + if (pnSize) *pnSize = cbStr; + return lpszResult; +} + +XMLNode::~XMLNode() { deleteNodeContent(); } + +int XMLNode::detachFromParent(XMLNodeData *d) +{ + XMLNode *pa=d->pParent->pChild; + int i=0; + while (((void*)(pa[i].d))!=((void*)d)) i++; + d->pParent->nChild--; + if (d->pParent->nChild) memmove(pa+i,pa+i+1,(d->pParent->nChild-i)*sizeof(XMLNode)); + else { free(pa); d->pParent->pChild=NULL; } + return removeOrderElement(d->pParent,eNodeChild,i); +} + +void XMLNode::deleteNodeContent(char force) +{ + if (!d) return; + (d->ref_count) --; + if ((d->ref_count==0)||force) + { + int i; + if (d->pParent) detachFromParent(d); + for(i=0; inChild; i++) { d->pChild[i].d->pParent=NULL; d->pChild[i].deleteNodeContent(force); } + free(d->pChild); + for(i=0; inText; i++) free((void*)d->pText[i]); + free(d->pText); + for(i=0; inClear; i++) free((void*)d->pClear[i].lpszValue); + free(d->pClear); + for(i=0; inAttribute; i++) + { + free((void*)d->pAttribute[i].lpszName); + if (d->pAttribute[i].lpszValue) free((void*)d->pAttribute[i].lpszValue); + } + free(d->pAttribute); + free(d->pOrder); + free((void*)d->lpszName); + free(d); + d=NULL; + } +} + +XMLNode XMLNode::addChild(XMLNode childNode, int pos) +{ + XMLNodeData *dc=childNode.d; + if ((!dc)||(!d)) return childNode; + if (dc->pParent) { if ((detachFromParent(dc)<=pos)&&(dc->pParent==d)) pos--; } else dc->ref_count++; + dc->pParent=d; +// int nc=d->nChild; +// d->pChild=(XMLNode*)myRealloc(d->pChild,(nc+1),memoryIncrease,sizeof(XMLNode)); + d->pChild=(XMLNode*)addToOrder(0,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=dc; + d->nChild++; + return childNode; +} + +void XMLNode::deleteAttribute(int i) +{ + if ((!d)||(i<0)||(i>=d->nAttribute)) return; + d->nAttribute--; + XMLAttribute *p=d->pAttribute+i; + free((void*)p->lpszName); + if (p->lpszValue) free((void*)p->lpszValue); + if (d->nAttribute) memmove(p,p+1,(d->nAttribute-i)*sizeof(XMLAttribute)); else { free(p); d->pAttribute=NULL; } +} + +void XMLNode::deleteAttribute(XMLAttribute *a){ if (a) deleteAttribute(a->lpszName); } +void XMLNode::deleteAttribute(XMLCSTR lpszName) +{ + int j=0; + getAttribute(lpszName,&j); + if (j) deleteAttribute(j-1); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,int i) +{ + if (!d) return NULL; + if (i>=d->nAttribute) + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + return NULL; + } + XMLAttribute *p=d->pAttribute+i; + if (p->lpszValue&&p->lpszValue!=lpszNewValue) free((void*)p->lpszValue); + p->lpszValue=lpszNewValue; + if (lpszNewName&&p->lpszName!=lpszNewName) { free((void*)p->lpszName); p->lpszName=lpszNewName; }; + return p; +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) +{ + if (oldAttribute) return updateAttribute_WOSD(newAttribute->lpszValue,newAttribute->lpszName,oldAttribute->lpszName); + return addAttribute_WOSD(newAttribute->lpszName,newAttribute->lpszValue); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName) +{ + int j=0; + getAttribute(lpszOldName,&j); + if (j) return updateAttribute_WOSD(lpszNewValue,lpszNewName,j-1); + else + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + else return addAttribute_WOSD(stringDup(lpszOldName),lpszNewValue); + } +} + +int XMLNode::indexText(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nText; + if (!lpszValue) { if (l) return 0; return -1; } + XMLCSTR *p=d->pText; + for (i=0; i=d->nText)) return; + d->nText--; + XMLCSTR *p=d->pText+i; + free((void*)*p); + if (d->nText) memmove(p,p+1,(d->nText-i)*sizeof(XMLCSTR)); else { free(p); d->pText=NULL; } + removeOrderElement(d,eNodeText,i); +} + +void XMLNode::deleteText(XMLCSTR lpszValue) { deleteText(indexText(lpszValue)); } + +XMLCSTR XMLNode::updateText_WOSD(XMLCSTR lpszNewValue, int i) +{ + if (!d) return NULL; + if (i>=d->nText) return addText_WOSD(lpszNewValue); + XMLCSTR *p=d->pText+i; + if (*p!=lpszNewValue) { free((void*)*p); *p=lpszNewValue; } + return lpszNewValue; +} + +XMLCSTR XMLNode::updateText_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) +{ + if (!d) return NULL; + int i=indexText(lpszOldValue); + if (i>=0) return updateText_WOSD(lpszNewValue,i); + return addText_WOSD(lpszNewValue); +} + +void XMLNode::deleteClear(int i) +{ + if ((!d)||(i<0)||(i>=d->nClear)) return; + d->nClear--; + XMLClear *p=d->pClear+i; + free((void*)p->lpszValue); + if (d->nClear) memmove(p,p+1,(d->nText-i)*sizeof(XMLClear)); else { free(p); d->pClear=NULL; } + removeOrderElement(d,eNodeClear,i); +} + +int XMLNode::indexClear(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nClear; + if (!lpszValue) { if (l) return 0; return -1; } + XMLClear *p=d->pClear; + for (i=0; ilpszValue); } + +XMLClear *XMLNode::updateClear_WOSD(XMLCSTR lpszNewContent, int i) +{ + if (!d) return NULL; + if (i>=d->nClear) + { + return addClear_WOSD(XMLClearTags[0].lpszOpen,lpszNewContent,XMLClearTags[0].lpszClose); + } + XMLClear *p=d->pClear+i; + if (lpszNewContent!=p->lpszValue) { free((void*)p->lpszValue); p->lpszValue=lpszNewContent; } + return p; +} + +XMLClear *XMLNode::updateClear_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) +{ + if (!d) return NULL; + int i=indexClear(lpszOldValue); + if (i>=0) return updateClear_WOSD(lpszNewValue,i); + return addClear_WOSD(lpszNewValue,XMLClearTags[0].lpszOpen,XMLClearTags[0].lpszClose); +} + +XMLClear *XMLNode::updateClear_WOSD(XMLClear *newP,XMLClear *oldP) +{ + if (oldP) return updateClear_WOSD(newP->lpszValue,oldP->lpszValue); + return NULL; +} + +XMLNode& XMLNode::operator=( const XMLNode& A ) +{ + // shallow copy + if (this != &A) + { + deleteNodeContent(); + d=A.d; + if (d) (d->ref_count) ++ ; + } + return *this; +} + +XMLNode::XMLNode(const XMLNode &A) +{ + // shallow copy + d=A.d; + if (d) (d->ref_count)++ ; +} + +int XMLNode::nChildNode(XMLCSTR name) const +{ + if (!d) return 0; + int i,j=0,n=d->nChild; + XMLNode *pc=d->pChild; + for (i=0; id->lpszName, name)==0) j++; + pc++; + } + return j; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int *j) const +{ + if (!d) return emptyXMLNode; + int i=0,n=d->nChild; + if (j) i=*j; + XMLNode *pc=d->pChild+i; + for (; id->lpszName, name)==0) + { + if (j) *j=i+1; + return *pc; + } + pc++; + } + return emptyXMLNode; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int j) const +{ + if (!d) return emptyXMLNode; + int i=0; + while (j-->0) getChildNode(name,&i); + return getChildNode(name,&i); +} + +int XMLNode::positionOfText (int i) const { if (i>=d->nText ) i=d->nText-1; return findPosition(d,i,eNodeText ); } +int XMLNode::positionOfClear (int i) const { if (i>=d->nClear) i=d->nClear-1; return findPosition(d,i,eNodeClear); } +int XMLNode::positionOfChildNode(int i) const { if (i>=d->nChild) i=d->nChild-1; return findPosition(d,i,eNodeChild); } +int XMLNode::positionOfText (XMLCSTR lpszValue) const { return positionOfText (indexText (lpszValue)); } +int XMLNode::positionOfClear(XMLCSTR lpszValue) const { return positionOfClear(indexClear(lpszValue)); } +int XMLNode::positionOfClear(XMLClear *a) const { if (a) return positionOfClear(a->lpszValue); return positionOfClear(); } +int XMLNode::positionOfChildNode(XMLNode x) const +{ + if ((!d)||(!x.d)) return -1; + XMLNodeData *dd=x.d; + XMLNode *pc=d->pChild; + int i=d->nChild; + while (i--) if (pc[i].d==dd) return findPosition(d,i,eNodeChild); + return -1; +} +int XMLNode::positionOfChildNode(XMLCSTR name, int count) const +{ + if (!name) return positionOfChildNode(count); + int j=0; + do { getChildNode(name,&j); if (j<0) return -1; } while (count--); + return findPosition(d,j-1,eNodeChild); +} + +XMLNode XMLNode::getChildNodeWithAttribute(XMLCSTR name,XMLCSTR attributeName,XMLCSTR attributeValue, int *k) const +{ + int i=0,j; + if (k) i=*k; + XMLNode x; + XMLCSTR t; + do + { + x=getChildNode(name,&i); + if (!x.isEmpty()) + { + if (attributeValue) + { + j=0; + do + { + t=x.getAttribute(attributeName,&j); + if (t&&(_tcsicmp(attributeValue,t)==0)) { if (k) *k=i+1; return x; } + } while (t); + } else + { + if (x.isAttributeSet(attributeName)) { if (k) *k=i+1; return x; } + } + } + } while (!x.isEmpty()); + return emptyXMLNode; +} + +// Find an attribute on an node. +XMLCSTR XMLNode::getAttribute(XMLCSTR lpszAttrib, int *j) const +{ + if (!d) return NULL; + int i=0,n=d->nAttribute; + if (j) i=*j; + XMLAttribute *pAttr=d->pAttribute+i; + for (; ilpszName, lpszAttrib)==0) + { + if (j) *j=i+1; + return pAttr->lpszValue; + } + pAttr++; + } + return NULL; +} + +char XMLNode::isAttributeSet(XMLCSTR lpszAttrib) const +{ + if (!d) return FALSE; + int i,n=d->nAttribute; + XMLAttribute *pAttr=d->pAttribute; + for (i=0; ilpszName, lpszAttrib)==0) + { + return TRUE; + } + pAttr++; + } + return FALSE; +} + +XMLCSTR XMLNode::getAttribute(XMLCSTR name, int j) const +{ + if (!d) return NULL; + int i=0; + while (j-->0) getAttribute(name,&i); + return getAttribute(name,&i); +} + +XMLNodeContents XMLNode::enumContents(int i) const +{ + XMLNodeContents c; + if (!d) { c.type=eNodeNULL; return c; } + if (inAttribute) + { + c.type=eNodeAttribute; + c.attrib=d->pAttribute[i]; + return c; + } + i-=d->nAttribute; + c.type=(XMLElementType)(d->pOrder[i]&3); + i=(d->pOrder[i])>>2; + switch (c.type) + { + case eNodeChild: c.child = d->pChild[i]; break; + case eNodeText: c.text = d->pText[i]; break; + case eNodeClear: c.clear = d->pClear[i]; break; + default: break; + } + return c; +} + +XMLCSTR XMLNode::getName() const { if (!d) return NULL; return d->lpszName; } +int XMLNode::nText() const { if (!d) return 0; return d->nText; } +int XMLNode::nChildNode() const { if (!d) return 0; return d->nChild; } +int XMLNode::nAttribute() const { if (!d) return 0; return d->nAttribute; } +int XMLNode::nClear() const { if (!d) return 0; return d->nClear; } +int XMLNode::nElement() const { if (!d) return 0; return d->nAttribute+d->nChild+d->nText+d->nClear; } +XMLClear XMLNode::getClear (int i) const { if ((!d)||(i>=d->nClear )) return emptyXMLClear; return d->pClear[i]; } +XMLAttribute XMLNode::getAttribute (int i) const { if ((!d)||(i>=d->nAttribute)) return emptyXMLAttribute; return d->pAttribute[i]; } +XMLCSTR XMLNode::getAttributeName (int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszName; } +XMLCSTR XMLNode::getAttributeValue(int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszValue; } +XMLCSTR XMLNode::getText (int i) const { if ((!d)||(i>=d->nText )) return NULL; return d->pText[i]; } +XMLNode XMLNode::getChildNode (int i) const { if ((!d)||(i>=d->nChild )) return emptyXMLNode; return d->pChild[i]; } +XMLNode XMLNode::getParentNode ( ) const { if ((!d)||(!d->pParent )) return emptyXMLNode; return XMLNode(d->pParent); } +char XMLNode::isDeclaration ( ) const { if (!d) return 0; return d->isDeclaration; } +char XMLNode::isEmpty ( ) const { return (d==NULL); } + +XMLNode XMLNode::addChild(XMLCSTR lpszName, char isDeclaration, int pos) + { return addChild_priv(0,stringDup(lpszName),isDeclaration,pos); } +XMLNode XMLNode::addChild_WOSD(XMLCSTR lpszName, char isDeclaration, int pos) + { return addChild_priv(0,lpszName,isDeclaration,pos); } +XMLAttribute *XMLNode::addAttribute(XMLCSTR lpszName, XMLCSTR lpszValue) + { return addAttribute_priv(0,stringDup(lpszName),stringDup(lpszValue)); } +XMLAttribute *XMLNode::addAttribute_WOSD(XMLCSTR lpszName, XMLCSTR lpszValuev) + { return addAttribute_priv(0,lpszName,lpszValuev); } +XMLCSTR XMLNode::addText(XMLCSTR lpszValue, int pos) + { return addText_priv(0,stringDup(lpszValue),pos); } +XMLCSTR XMLNode::addText_WOSD(XMLCSTR lpszValue, int pos) + { return addText_priv(0,lpszValue,pos); } +XMLClear *XMLNode::addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) + { return addClear_priv(0,stringDup(lpszValue),lpszOpen,lpszClose,pos); } +XMLClear *XMLNode::addClear_WOSD(XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) + { return addClear_priv(0,lpszValue,lpszOpen,lpszClose,pos); } +XMLCSTR XMLNode::updateName(XMLCSTR lpszName) + { return updateName_WOSD(stringDup(lpszName)); } +XMLAttribute *XMLNode::updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) + { return updateAttribute_WOSD(stringDup(newAttribute->lpszValue),stringDup(newAttribute->lpszName),oldAttribute->lpszName); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,int i) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),i); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),lpszOldName); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, int i) + { return updateText_WOSD(stringDup(lpszNewValue),i); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateText_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewContent, int i) + { return updateClear_WOSD(stringDup(lpszNewContent),i); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateClear_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLClear *newP,XMLClear *oldP) + { return updateClear_WOSD(stringDup(newP->lpszValue),oldP->lpszValue); } + +void XMLNode::setGlobalOptions(char _guessUnicodeChars, char _strictUTF8Parsing, char _dropWhiteSpace) +{ + guessUnicodeChars=_guessUnicodeChars; dropWhiteSpace=_dropWhiteSpace; strictUTF8Parsing=_strictUTF8Parsing; +#ifndef _XMLUNICODE + if (_strictUTF8Parsing) XML_ByteTable=XML_utf8ByteTable; else XML_ByteTable=XML_asciiByteTable; +#endif +} + +char XMLNode::guessUTF8ParsingParameterValue(void *buf,int l, char useXMLEncodingAttribute) +{ +#ifdef _XMLUNICODE + return 0; +#else + if (l<25) return 0; + if (myIsTextUnicode(buf,l)) return 0; + unsigned char *b=(unsigned char*)buf; + if ((b[0]==0xef)&&(b[1]==0xbb)&&(b[2]==0xbf)) return 1; + + // Match utf-8 model ? + int i=0; + while (i>2 ]; + *(curr++)=base64EncodeTable[(inbuf[0]<<4)&0x3F]; + *(curr++)=base64Fillchar; + *(curr++)=base64Fillchar; + } else if (eLen==2) + { + j=(inbuf[0]<<8)|inbuf[1]; + *(curr++)=base64EncodeTable[ j>>10 ]; + *(curr++)=base64EncodeTable[(j>> 4)&0x3f]; + *(curr++)=base64EncodeTable[(j<< 2)&0x3f]; + *(curr++)=base64Fillchar; + } + *(curr++)=0; + return (XMLSTR)buf; +} + +unsigned int XMLParserBase64Tool::decodeSize(XMLCSTR data,XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + int size=0; + unsigned char c; + //skip any extra characters (e.g. newlines or spaces) + while (*data) + { +#ifdef _XMLUNICODE + if (*data>255) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + c=base64DecodeTable[(unsigned char)(*data)]; + if (c<97) size++; + else if (c==98) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } + data++; + } + if (xe&&(size%4!=0)) *xe=eXMLErrorBase64DataSizeIsNotMultipleOf4; + if (size==0) return 0; + do { data--; size--; } while(*data==base64Fillchar); size++; + return (unsigned int)((size*3)/4); +} + +unsigned char XMLParserBase64Tool::decode(XMLCSTR data, unsigned char *buf, int len, XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + int i=0,p=0; + unsigned char d,c; + for(;;) + { + +#ifdef _XMLUNICODE +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { \ + if (data[i]>255){ c=98; break; } \ + c=base64DecodeTable[(unsigned char)data[i++]]; \ + }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#else +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { c=base64DecodeTable[(unsigned char)data[i++]]; }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { return 2; } + if (c==96) + { + if (p==(int)len) return 2; + if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; + return 1; + } + + BASE64DECODE_READ_NEXT_CHAR(d) + if ((d==99)||(d==96)) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) { if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; return 0; } + buf[p++]=(c<<2)|((d>>4)&0x3); + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (c==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (c==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=((d<<4)&0xf0)|((c>>2)&0xf); + + BASE64DECODE_READ_NEXT_CHAR(d) + if (d==99 ) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (d==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (d==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=((c<<6)&0xc0)|d; + } +} +#undef BASE64DECODE_READ_NEXT_CHAR + +void XMLParserBase64Tool::alloc(int newsize) +{ + if ((!buf)&&(newsize)) { buf=malloc(newsize); buflen=newsize; return; } + if (newsize>buflen) { buf=realloc(buf,newsize); buflen=newsize; } +} + +unsigned char *XMLParserBase64Tool::decode(XMLCSTR data, int *outlen, XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + unsigned int len=decodeSize(data,xe); + if (outlen) *outlen=len; + if (!len) return NULL; + alloc(len+1); + if(!decode(data,(unsigned char*)buf,len,xe)){ return NULL; } + return (unsigned char*)buf; +} + diff --git a/dol/src/dol/visitor/hds/lib/xmlParser.h b/dol/src/dol/visitor/hds/lib/xmlParser.h new file mode 100644 index 0000000..7da09a5 --- /dev/null +++ b/dol/src/dol/visitor/hds/lib/xmlParser.h @@ -0,0 +1,529 @@ +/** + **************************************************************************** + *

XML.c - implementation file for basic XML parser written in ANSI C++ + * for portability. It works by using recursion and a node tree for breaking + * down the elements of an XML document.

+ * + * @version V2.23 + * @author Frank Vanden Berghen + * + * BSD license: + * Copyright (c) 2002, Frank Vanden Berghen + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Frank Vanden Berghen nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************** + */ +#ifndef __INCLUDE_XML_NODE__ +#define __INCLUDE_XML_NODE__ + +#include + +#ifdef _UNICODE +// If you comment the next "define" line then the library will never "switch to" _UNICODE (wchar_t*) mode (16/32 bits per characters). +// This is useful when you get error messages like: +// 'XMLNode::openFileHelper' : cannot convert parameter 2 from 'const char [5]' to 'const wchar_t *' +// The _XMLUNICODE preprocessor variable force the XMLParser library into either utf16/32-mode (the proprocessor variable +// must be defined) or utf8-mode(the pre-processor variable must be undefined). +#define _XMLUNICODE +#endif + +#if defined(WIN32) || defined(UNDER_CE) +// comment the next line if you are under windows and the compiler is not Microsoft Visual Studio (6.0 or .NET) +#define _XMLWINDOWS +#endif + +#ifdef DLLENTRY +#undef DLLENTRY +#endif +#ifdef _USE_XMLPARSER_DLL +#ifdef _DLL_EXPORTS_ +#define DLLENTRY __declspec(dllexport) +#else +#define DLLENTRY __declspec(dllimport) +#endif +#else +#define DLLENTRY +#endif + +// uncomment the next line if you want no support for wchar_t* (no need for the or libraries anymore to compile) +//#define XML_NO_WIDE_CHAR + +#ifdef XML_NO_WIDE_CHAR +#undef _XMLWINDOWS +#undef _XMLUNICODE +#endif + +#ifdef _XMLWINDOWS +#include +#else +#define DLLENTRY +#ifndef XML_NO_WIDE_CHAR +#include // to have 'wcsrtombs' for ANSI version + // to have 'mbsrtowcs' for UNICODE version +#endif +#endif + +// Some common types for char set portable code +#ifdef _XMLUNICODE + #ifndef _T + #define _T(c) L ## c + #endif + #define XMLCSTR const wchar_t * + #define XMLSTR wchar_t * + #define XMLCHAR wchar_t +#else + #ifndef _T + #define _T(c) c + #endif + #define XMLCSTR const char * + #define XMLSTR char * + #define XMLCHAR char +#endif +#ifndef FALSE + #define FALSE 0 +#endif /* FALSE */ +#ifndef TRUE + #define TRUE 1 +#endif /* TRUE */ + + +// Enumeration for XML parse errors. +typedef enum XMLError +{ + eXMLErrorNone = 0, + eXMLErrorMissingEndTag, + eXMLErrorEmpty, + eXMLErrorFirstNotStartTag, + eXMLErrorMissingTagName, + eXMLErrorMissingEndTagName, + eXMLErrorNoMatchingQuote, + eXMLErrorUnmatchedEndTag, + eXMLErrorUnmatchedEndClearTag, + eXMLErrorUnexpectedToken, + eXMLErrorInvalidTag, + eXMLErrorNoElements, + eXMLErrorFileNotFound, + eXMLErrorFirstTagNotFound, + eXMLErrorUnknownCharacterEntity, + eXMLErrorCharConversionError, + eXMLErrorCannotOpenWriteFile, + eXMLErrorCannotWriteFile, + + eXMLErrorBase64DataSizeIsNotMultipleOf4, + eXMLErrorBase64DecodeIllegalCharacter, + eXMLErrorBase64DecodeTruncatedData, + eXMLErrorBase64DecodeBufferTooSmall +} XMLError; + +// Enumeration used to manage type of data. Use in conjunction with structure XMLNodeContents +typedef enum XMLElementType +{ + eNodeChild=0, + eNodeAttribute=1, + eNodeText=2, + eNodeClear=3, + eNodeNULL=4 +} XMLElementType; + +// Structure used to obtain error details if the parse fails. +typedef struct XMLResults +{ + enum XMLError error; + int nLine,nColumn; +} XMLResults; + +// Structure for XML clear (unformatted) node (usually comments) +typedef struct { + XMLCSTR lpszValue; XMLCSTR lpszOpenTag; XMLCSTR lpszCloseTag; +} XMLClear; + +// Structure for XML attribute. +typedef struct { + XMLCSTR lpszName; XMLCSTR lpszValue; +} XMLAttribute; + +// Structure for XML clear tags. +typedef struct { + XMLCSTR lpszOpen; int openTagLen; XMLCSTR lpszClose; +} ALLXMLClearTag; + +struct XMLNodeContents; + +typedef struct DLLENTRY XMLNode +{ + private: + + struct XMLNodeDataTag; + + // protected constructors: use one of these four methods to get your first instance of XMLNode: + // - parseString + // - parseFile + // - openFileHelper + // - createXMLTopNode + XMLNode(struct XMLNodeDataTag *pParent, XMLCSTR lpszName, char isDeclaration); + XMLNode(struct XMLNodeDataTag *p); + + public: + + // You can create your first instance of XMLNode with these 4 functions: + // (see complete explanation of parameters below) + + static XMLNode createXMLTopNode(XMLCSTR lpszName, char isDeclaration=FALSE); + static XMLNode parseString (XMLCSTR lpXMLString, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + static XMLNode parseFile (XMLCSTR filename, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + static XMLNode openFileHelper(XMLCSTR filename, XMLCSTR tag=NULL ); + + // The tag parameter should be the name of the first tag inside the XML file. + // If the tag parameter is omitted, the 3 functions return a node that represents + // the head of the xml document including the declaration term (). + + // The "openFileHelper" reports to the screen all the warnings & errors that occurred during + // parsing of the XML file. Since each application has its own way to report and deal with errors, + // you should rather use the "parseFile" function to parse XML files and program yourself thereafter + // an "error reporting" tailored for your needs (instead of using the very crude "error reporting" + // mechanism included inside the "openFileHelper" function). + + // If the XML document is corrupted: + // * The "openFileHelper" method will: + // - display an error message on the console (or inside a messageBox for windows). + // - stop execution (exit). + // I suggest that you write your own "openFileHelper" method tailored to your needs. + // * The 2 other methods will initialize the "pResults" variable with some information that + // can be used to trace the error. + // * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as + // explained inside the note at the beginning of the "xmlParser.cpp" file. + // You can have a user-friendly explanation of the parsing error with this function: + static XMLCSTR getError(XMLError error); + static XMLCSTR getVersion(); + static ALLXMLClearTag* getClearTagTable(); + + XMLCSTR getName() const; // name of the node + XMLCSTR getText(int i=0) const; // return ith text field + int nText() const; // nbr of text field + XMLNode getParentNode() const; // return the parent node + XMLNode getChildNode(int i=0) const; // return ith child node + XMLNode getChildNode(XMLCSTR name, int i) const; // return ith child node with specific name + // (return an empty node if failing) + XMLNode getChildNode(XMLCSTR name, int *i=NULL) const; // return next child node with specific name + // (return an empty node if failing) + XMLNode getChildNodeWithAttribute(XMLCSTR tagName, // return child node with specific name/attribute + XMLCSTR attributeName, // (return an empty node if failing) + XMLCSTR attributeValue=NULL, // + int *i=NULL) const; // + int nChildNode(XMLCSTR name) const; // return the number of child node with specific name + int nChildNode() const; // nbr of child node + XMLAttribute getAttribute(int i=0) const; // return ith attribute + XMLCSTR getAttributeName(int i=0) const; // return ith attribute name + XMLCSTR getAttributeValue(int i=0) const; // return ith attribute value + char isAttributeSet(XMLCSTR name) const; // test if an attribute with a specific name is given + XMLCSTR getAttribute(XMLCSTR name, int i) const; // return ith attribute content with specific name + // (return a NULL if failing) + XMLCSTR getAttribute(XMLCSTR name, int *i=NULL) const; // return next attribute content with specific name + // (return a NULL if failing) + int nAttribute() const; // nbr of attribute + XMLClear getClear(int i=0) const; // return ith clear field (comments) + int nClear() const; // nbr of clear field + XMLSTR createXMLString(int nFormat=1, int *pnSize=NULL) const; // create XML string starting from current XMLNode + // if nFormat==0, no formatting is required + // otherwise this returns an user friendly XML string from a + // given element with appropriate white spaces and carriage returns. + // if pnSize is given it returns the size in character of the string. + XMLError writeToFile(XMLCSTR filename, const char *encoding=NULL, char nFormat=1) const; + // save the content of an xmlNode inside a file. + // the nFormat parameter has the same meaning as in the + // createXMLString function. If "strictUTF8Parsing=1", the + // the encoding parameter is ignored and always set to + // "utf-8". If "_XMLUNICODE=1", the encoding parameter is + // ignored and always set to "utf-16". + XMLNodeContents enumContents(int i) const; // enumerate all the different contents (attribute,child,text, + // clear) of the current XMLNode. The order is reflecting + // the order of the original file/string. + // NOTE: 0 <= i < nElement(); + int nElement() const; // nbr of different contents for current node + char isEmpty() const; // is this node Empty? + char isDeclaration() const; // is this node a declaration + +// to allow shallow/fast copy: + ~XMLNode(); + XMLNode(const XMLNode &A); + XMLNode& operator=( const XMLNode& A ); + + XMLNode(): d(NULL){}; + static XMLNode emptyXMLNode; + static XMLClear emptyXMLClear; + static XMLAttribute emptyXMLAttribute; + + // The following functions allows you to create from scratch (or update) a XMLNode structure + // Start by creating your top node with the "createXMLTopNode" function and then add new nodes with the "addChild" function. + // The parameter 'pos' gives the position where the childNode, the text or the XMLClearTag will be inserted. + // The default value (pos=-1) inserts at the end. The value (pos=0) insert at the beginning (Insertion at the beginning is slower than at the end). + // REMARK: 0 <= pos < nChild()+nText()+nClear() + XMLNode addChild(XMLCSTR lpszName, char isDeclaration=FALSE, int pos=-1); + XMLAttribute *addAttribute(XMLCSTR lpszName, XMLCSTR lpszValuev); + XMLCSTR addText(XMLCSTR lpszValue, int pos=-1); + XMLClear *addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, int pos=-1); + // default values: lpszOpen=XMLNode::getClearTagTable()->lpszOpen; + // lpszClose=XMLNode::getClearTagTable()->lpszClose; + XMLNode addChild(XMLNode nodeToAdd, int pos=-1); // If the "nodeToAdd" has some parents, it will be detached + // from it's parents before being attached to the current XMLNode + // Some update functions: + XMLCSTR updateName(XMLCSTR lpszName); // change node's name + XMLAttribute *updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); // if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName=NULL,int i=0); // if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName); // set lpszNewName=NULL if you don't want to change the name of the attribute + // if the attribute to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, int i=0); // if the text to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); // if the text to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewContent, int i=0); // if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLClear *newP,XMLClear *oldP); // if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); // if the clearTag to update is missing, a new one will be added + + // Some deletion functions: + void deleteNodeContent(char force=0); // delete the content of this XMLNode and the subtree. + // if force=0, while (references to this node still exist), no memory free occurs + // if force=1, always delete the content of this XMLNode and the subtree and free associated memory + void deleteAttribute(XMLCSTR lpszName); + void deleteAttribute(int i=0); + void deleteAttribute(XMLAttribute *anAttribute); + void deleteText(int i=0); + void deleteText(XMLCSTR lpszValue); + void deleteClear(int i=0); + void deleteClear(XMLClear *p); + void deleteClear(XMLCSTR lpszValue); + + // The strings given as parameters for the following add and update methods (all these methods have + // a name with the postfix "_WOSD" that means "WithOut String Duplication" ) will be free'd by the + // XMLNode class. For example, it means that this is incorrect: + // xNode.addText_WOSD("foo"); + // xNode.updateAttribute_WOSD("#newcolor" ,NULL,"color"); + // In opposition, this is correct: + // xNode.addText("foo"); + // xNode.addText_WOSD(stringDup("foo")); + // xNode.updateAttribute("#newcolor" ,NULL,"color"); + // xNode.updateAttribute_WOSD(stringDup("#newcolor"),NULL,"color"); + // Typically, you will never do: + // char *b=(char*)malloc(...); + // xNode.addText(b); + // free(b); + // ... but rather: + // char *b=(char*)malloc(...); + // xNode.addText_WOSD(b); + // ('free(b)' is performed by the XMLNode class) + + static XMLNode createXMLTopNode_WOSD(XMLCSTR lpszName, char isDeclaration=FALSE); + XMLNode addChild_WOSD(XMLCSTR lpszName, char isDeclaration=FALSE, int pos=-1); + XMLAttribute *addAttribute_WOSD(XMLCSTR lpszName, XMLCSTR lpszValue); + XMLCSTR addText_WOSD(XMLCSTR lpszValue, int pos=-1); + XMLClear *addClear_WOSD(XMLCSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, int pos=-1); + + XMLCSTR updateName_WOSD(XMLCSTR lpszName); + XMLAttribute *updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); + XMLAttribute *updateAttribute_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszNewName=NULL,int i=0); + XMLAttribute *updateAttribute_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName); + XMLCSTR updateText_WOSD(XMLCSTR lpszNewValue, int i=0); + XMLCSTR updateText_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); + XMLClear *updateClear_WOSD(XMLCSTR lpszNewContent, int i=0); + XMLClear *updateClear_WOSD(XMLClear *newP,XMLClear *oldP); + XMLClear *updateClear_WOSD(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); + + // These are some useful functions when you want to insert a childNode, a text or a XMLClearTag in the + // middle (at a specified position) of a XMLNode tree already constructed. The value returned by these + // methods is to be used as last parameter (parameter 'pos') of addChild, addText or addClear. + int positionOfText(int i=0) const; + int positionOfText(XMLCSTR lpszValue) const; + int positionOfClear(int i=0) const; + int positionOfClear(XMLCSTR lpszValue) const; + int positionOfClear(XMLClear *a) const; + int positionOfChildNode(int i=0) const; + int positionOfChildNode(XMLNode x) const; + int positionOfChildNode(XMLCSTR name, int i=0) const; // return the position of the ith childNode with the specified name + // if (name==NULL) return the position of the ith childNode + + // The setGlobalOptions function allows you to change two global parameters that affect string&file + // parsing. First of all, you most-probably will never have to change these 2 global parameters. + // About the "guessUnicodeChars" parameter: + // If "guessUnicodeChars=1" and if this library is compiled in UNICODE mode, then the + // "parseFile" and "openFileHelper" functions will test if the file contains ASCII + // characters. If this is the case, then the file will be loaded and converted in memory to + // UNICODE before being parsed. If "guessUnicodeChars=0", no conversion will + // be performed. + // + // If "guessUnicodeChars=1" and if this library is compiled in ASCII/UTF8 mode, then the + // "parseFile" and "openFileHelper" functions will test if the file contains UNICODE + // characters. If this is the case, then the file will be loaded and converted in memory to + // ASCII/UTF8 before being parsed. If "guessUnicodeChars=0", no conversion will + // be performed + // + // Sometime, it's useful to set "guessUnicodeChars=0" to disable any conversion + // because the test to detect the file-type (ASCII/UTF8 or UNICODE) may fail (rarely). + // + // About the "strictUTF8Parsing" parameter: + // If "strictUTF8Parsing=0" then we assume that all characters have the same length of 1 byte. + // If "strictUTF8Parsing=1" then the characters have different lengths (from 1 byte to 4 bytes) + // depending on the content of the first byte of the character. + // About the "dropWhiteSpace" parameter: + // + + static void setGlobalOptions(char guessUnicodeChars=1, char strictUTF8Parsing=1, char dropWhiteSpace=1); + + // The next function try to guess if the character encoding is UTF-8. You most-probably will never + // have to use this function. It then returns the appropriate value of the global parameter + // "strictUTF8Parsing" described above. The guess is based on the content of a buffer of length + // "bufLen" bytes that contains the first bytes (minimum 25 bytes; 200 bytes is a good value) of the + // file to be parsed. The "openFileHelper" function is using this function to automatically compute + // the value of the "strictUTF8Parsing" global parameter. There are several heuristics used to do the + // guess. One of the heuristic is based on the "encoding" attribute. The original XML specifications + // forbids to use this attribute to do the guess but you can still use it if you set + // "useXMLEncodingAttribute" to 1 (this is the default behavior and the behavior of most parsers). + + static char guessUTF8ParsingParameterValue(void *buffer, int bufLen, char useXMLEncodingAttribute=1); + + private: + +// these are functions and structures used internally by the XMLNode class (don't bother about them): + + typedef struct XMLNodeDataTag // to allow shallow copy and "intelligent/smart" pointers (automatic delete): + { + XMLCSTR lpszName; // Element name (=NULL if root) + int nChild, // Number of child nodes + nText, // Number of text fields + nClear, // Number of Clear fields (comments) + nAttribute; // Number of attributes + char isDeclaration; // Whether node is an XML declaration - '' + struct XMLNodeDataTag *pParent; // Pointer to parent element (=NULL if root) + XMLNode *pChild; // Array of child nodes + XMLCSTR *pText; // Array of text fields + XMLClear *pClear; // Array of clear fields + XMLAttribute *pAttribute; // Array of attributes + int *pOrder; // order of the child_nodes,text_fields,clear_fields + int ref_count; // for garbage collection (smart pointers) + } XMLNodeData; + XMLNodeData *d; + + char parseClearTag(void *px, ALLXMLClearTag *pa); + char maybeAddTxT(void *pa, XMLCSTR tokenPStr); + int ParseXMLElement(void *pXML); + void *addToOrder(int memInc, int *_pos, int nc, void *p, int size, XMLElementType xtype); + int indexText(XMLCSTR lpszValue) const; + int indexClear(XMLCSTR lpszValue) const; + XMLNode addChild_priv(int,XMLCSTR,char,int); + XMLAttribute *addAttribute_priv(int,XMLCSTR,XMLCSTR); + XMLCSTR addText_priv(int,XMLCSTR,int); + XMLClear *addClear_priv(int,XMLCSTR,XMLCSTR,XMLCSTR,int); + static inline int findPosition(XMLNodeData *d, int index, XMLElementType xtype); + static int CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat); + static int removeOrderElement(XMLNodeData *d, XMLElementType t, int index); + static void exactMemory(XMLNodeData *d); + static int detachFromParent(XMLNodeData *d); +} XMLNode; + +// This structure is given by the function "enumContents". +typedef struct XMLNodeContents +{ + // This dictates what's the content of the XMLNodeContent + enum XMLElementType type; + // should be an union to access the appropriate data. + // compiler does not allow union of object with constructor... too bad. + XMLNode child; + XMLAttribute attrib; + XMLCSTR text; + XMLClear clear; + +} XMLNodeContents; + +DLLENTRY void free_XMLDLL(void *t); // {free(t);} + +// Duplicate (copy in a new allocated buffer) the source string. This is +// a very handy function when used with all the "XMLNode::*_WOSD" functions. +// (If (cbData!=0) then cbData is the number of chars to duplicate) +DLLENTRY XMLSTR stringDup(XMLCSTR source, int cbData=0); + +// The 3 following functions are processing strings so that all the characters +// &,",',<,> are replaced by their XML equivalent: &, ", ', <, >. +// These 3 functions are useful when creating from scratch an XML file using the +// "printf", "fprintf", "cout",... functions. If you are creating from scratch an +// XML file using the provided XMLNode class you cannot use these functions (the +// XMLNode class does the processing job for you during rendering). The second +// function ("toXMLStringFast") allows you to re-use the same output buffer +// for all the conversions so that only a few memory allocations are performed. +// If the output buffer is too small to contain thee resulting string, it will +// be enlarged. +DLLENTRY XMLSTR toXMLString(XMLCSTR source); +DLLENTRY XMLSTR toXMLStringFast(XMLSTR *destBuffer,int *destSz, XMLCSTR source); + +// you should not use this one (there is a possibility of "destination-buffer-overflow"): +DLLENTRY XMLSTR toXMLString(XMLSTR dest,XMLCSTR source); + +// Below is a class that allows you to include any binary data (images, sounds,...) +// into an XML document using "Base64 encoding". This class is completely +// separated from the rest of the xmlParser library and can be removed without any problem. +// To include some binary data into an XML file, you must convert the binary data into +// standard text (using "encode"). To retrieve the original binary data from the +// b64-encoded text included inside the XML file use "decode". Alternatively, these +// functions can also be used to "encrypt/decrypt" some critical data contained inside +// the XML. + +class DLLENTRY XMLParserBase64Tool +{ +public: + XMLParserBase64Tool(): buf(NULL),buflen(0){} + ~XMLParserBase64Tool(); + + void freeBuffer(); + + // returns the length of the base64 string that encodes a data buffer of size inBufLen bytes. + // If "formatted" parameter is true, some space will be reserved for a carriage-return every 72 chars. + static int encodeLength(int inBufLen, char formatted=0); + + // The "base64Encode" function returns a string containing the base64 encoding of "inByteLen" bytes + // from "inByteBuf". If "formatted" parameter is true, then there will be a carriage-return every 72 chars. + // The string will be free'd when the XMLParserBase64Tool object is deleted. + // All returned strings are sharing the same memory space. + XMLSTR encode(unsigned char *inByteBuf, unsigned int inByteLen, char formatted=0); + + // returns the number of bytes which will be decoded from "inString". + static unsigned int decodeSize(XMLCSTR inString, XMLError *xe=NULL); + + // returns a pointer to a buffer containing the binary data decoded from "inString" + // If "inString" is malformed NULL will be returned + // The output buffer will be free'd when the XMLParserBase64Tool object is deleted. + // All output buffer are sharing the same memory space. + unsigned char* decode(XMLCSTR inString, int *outByteLen=NULL, XMLError *xe=NULL); + + // The next function is deprecated. + // decodes data from "inString" to "outByteBuf". You need to provide the size (in byte) of "outByteBuf" + // in "inMaxByteOutBuflen". If "outByteBuf" is not large enough or if data is malformed, then "FALSE" + // will be returned; otherwise "TRUE". + static unsigned char decode(XMLCSTR inString, unsigned char *outByteBuf, int inMaxByteOutBuflen, XMLError *xe=NULL); + +private: + void *buf; + int buflen; + void alloc(int newsize); +}; + +#undef DLLENTRY + +#endif diff --git a/dol/src/dol/visitor/hdsd/HdsdMakefileVisitor.java b/dol/src/dol/visitor/hdsd/HdsdMakefileVisitor.java new file mode 100644 index 0000000..8458b32 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdMakefileVisitor.java @@ -0,0 +1,134 @@ +package dol.visitor.hdsd; + + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Process; +import dol.visitor.MapVisitor; + +/** + * Visitor that is used to generate + * a HdS package Makefile for a distributed simulation. + */ +public class HdsdMakefileVisitor extends MapVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public HdsdMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given mapping. + * + * @param x mapping that needs to be rendered. + */ + public void visitComponent(Mapping x) { + try { + String filename = _dir + "/" + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("CXX = g++"); + ps.println("CC = g++"); + ps.println(); + ps.println("PREPROC_MACROS = -D __DOL_ETHZ_GEN__ -DINCLUDE_PROFILER"); + ps.println(); + ps.println("SYSTEMC_INC = -I" + _ui.getSystemCINC()); + ps.println("SYSTEMC_LIB = " + _ui.getSystemCLIB()); + ps.println("MY_LIB_INC = -Ilib -Iscd -Isc_wrappers -Iprocesses"); + ps.println("VPATH = lib:scd:scd/fsm:sc_wrappers:processes"); + ps.println(); + ps.println("# append '-rdynamic' for name resolution in exception stack traces"); + ps.println("# append '-g' for debugging"); + ps.println("CXXFLAGS = -O0 $(PREPROC_MACROS) $(SYSTEMC_INC) $(MY_LIB_INC)"); + ps.println("CFLAGS = $(CXXFLAGS)"); + ps.println(); + + // scd library objects + ps.println("# scd library objects"); + ps.println("SCD_OBJS = scd_logging.o scd_exception.o scd_socket.o scd_sock_poller.o \\"); + ps.println("\tscd_init_listener.o scd_in_connector.o scd_out_connector.o \\"); + ps.println("\tscd_command.o scd_command_reader.o scd_command_writer.o \\"); + ps.println("\tscd_simulator.o scd_chan_man.o scd_chan_wrapper.o \\"); + ps.println("\tscd_cont_man_master.o scd_cont_man_slave.o scd_cont_slave_wrapper.o \\"); + ps.println("\tscd_rem_fifo_in.o scd_rem_fifo_out.o scd_cont_fsm.o \\"); + ps.println("\tscd_sts_base.o scd_sts_init.o scd_sts_busy.o scd_sts_idle.o \\"); + ps.println("\tscd_sts_done.o scd_sts_time_ack.o scd_sts_time.o scd_sts_term_ack.o \\"); + ps.println("\tscd_sts_terminated.o scd_sts_fail.o scd_sts_failed.o \\"); + ps.println("\tscd_stm_base.o scd_stm_init.o scd_stm_busy.o scd_stm_idle.o \\"); + ps.println("\tscd_stm_done.o scd_stm_time_req.o scd_stm_time.o scd_stm_term_req.o \\"); + ps.println("\tscd_stm_terminate.o scd_stm_terminated.o \\"); + ps.println("\tscd_stm_fail.o scd_stm_failed.o \\"); + ps.println("\tscd_stsw_base.o scd_stsw_init.o scd_stsw_busy.o \\"); + ps.println("\tscd_stsw_idle.o scd_stsw_done.o scd_stsw_time_req.o \\"); + ps.println("\tscd_stsw_time_ack.o scd_stsw_term_req.o scd_stsw_term_ack.o \\"); + ps.println("\tscd_stsw_terminate.o scd_stsw_terminated.o \\"); + ps.println("\tscd_stsw_fail.o scd_stsw_failed.o"); + ps.println(); + + // process objects + ps.println("# process objects"); + ps.print("PROCESS_OBJS = dol.o "); + Iterator i = x.getProcessList().iterator(); + Set pSet = new HashSet(); + while (i.hasNext()) { + String basename = i.next().getBasename(); + if (pSet.add(basename)) + ps.print(basename + "_wrapper.o "); + } + ps.println(); + ps.println(); + + // target all + Iterator iter = x.getProcessorList().iterator(); + String processorList = new String(); + while (iter.hasNext()) + { + Processor p = iter.next(); + if (x.getProcessList().isEmpty()) + continue; + processorList += " scd_" + p.getName(); + } + ps.println("all:" + processorList); + ps.println(); + + // processor targets + iter = x.getProcessorList().iterator(); + while (iter.hasNext()) + { + Processor p = iter.next(); + if (x.getProcessList().isEmpty()) + continue; + ps.println("scd_" + p.getName() + ": scd_" + + p.getName() + ".o $(PROCESS_OBJS)" + + " $(SCD_OBJS)"); + ps.println("\t$(CXX) $(CXXFLAGS) -o $@ $^ $(SYSTEMC_LIB)"); + ps.println(); + } + + // clean target + ps.println("clean:"); + ps.println("\t-rm -f *.o core core.* *.core" + processorList); + + } catch (Exception e) { + System.out.println("HdsdMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + + } + + protected String _dir = null; +} + diff --git a/dol/src/dol/visitor/hdsd/HdsdModuleArchVisitor.java b/dol/src/dol/visitor/hdsd/HdsdModuleArchVisitor.java new file mode 100644 index 0000000..4b89646 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdModuleArchVisitor.java @@ -0,0 +1,491 @@ +package dol.visitor.hdsd; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.Vector; + +import dol.datamodel.architecture.Configuration; +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.Resource; +import dol.main.UserInterface; +import dol.util.CodePrintStream; +import dol.visitor.ArchiVisitor; + +/** + * Visitor that generates the main program for one distributed simulator. + */ +public class HdsdModuleArchVisitor extends ArchiVisitor { + + /** + * Constructor. + * @param map mapping of this code generation + * @param dir path of this file + */ + public HdsdModuleArchVisitor(Mapping map, String dir) { + _map = map; + _dir = dir; + _channels = new LinkedHashSet(); + _remInChannels = new LinkedHashSet(); + _remOutChannels = new LinkedHashSet(); + _locChannels = new LinkedHashSet(); + } + + /** + * + * @param x Processor that needs to be rendered + */ + public void visitComponent(Processor x) { + + // get channels for this processor + _getChannels(x); + + try { + String filename = _dir + "/" + "scd_" + + x.getName() + ".cpp"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#include "); + + /* begin of profiling: standard i/o file handling routines */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("#include \"dol_sched_if.h\""); + _mainPS.printPrefixln("#include \"simple_fifo.h\""); + _mainPS.printPrefixln("#include \"scd_logging.h\""); + _mainPS.printPrefixln("#include \"scd_simulator.h\""); + _mainPS.printPrefixln("#include \"scd_rem_fifo_in.h\""); + _mainPS.printPrefixln("#include \"scd_rem_fifo_out.h\""); + _mainPS.println(); + + // include process files + Iterator pIt = x.getProcessList().iterator(); + Vector pList = new Vector(); + while (pIt.hasNext()) { + Process p = pIt.next(); + String basename = p.getBasename(); + if (!pList.contains(basename)) { + _mainPS.printPrefixln("#include \"" + p.getBasename() + + "_wrapper.h\""); + pList.add(basename); + } + } + _mainPS.println(); + + // namespaces + _mainPS.printPrefixln("using namespace std;"); + _mainPS.printPrefixln("using sc_core::sc_module;"); + _mainPS.printPrefixln("using sc_core::sc_event;"); + _mainPS.printPrefixln("using sc_core::sc_prim_channel;"); + + + /* begin of profiling: global variables */ + _mainPS.println(); + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("#define PROFILER_OUTPUT_FILENAME " + + "\"profile_" + x.getName() + ".txt\""); + _mainPS.printPrefixln("FILE *profiler_output_file;"); + _mainPS.printPrefixln("unsigned int profiler_event_counter;"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.println(); + _mainPS.printPrefixln("class sc_application : public sc_module "); + _mainPS.printLeftBracket(); + + _mainPS.printPrefixln("public:"); + _mainPS.printPrefixln("SC_HAS_PROCESS(sc_application);"); + + //declare processes + _mainPS.println(); + pIt = x.getProcessList().iterator(); + while (pIt.hasNext()) { + Process p = pIt.next(); + _mainPS.printPrefixln(p.getBasename() + "_wrapper " + + p.getName() + "_ins"+ ";"); + _mainPS.printPrefixln("sc_event " + p.getName() + + "_event;"); + } + _mainPS.println(); + + //define the scheduler + _mainPS.printPrefixln("sc_event sched_event;"); + _mainPS.printPrefixln("list eventList;"); + _mainPS.printPrefixln("list::iterator iter;"); + _mainPS.println(); + + //declare channels + _mainPS.println(); + Iterator cIt; + cIt = _locChannels.iterator(); + while (cIt.hasNext()) + _mainPS.printPrefixln("fifo " + cIt.next() + "_ins;"); + cIt = _remInChannels.iterator(); + while (cIt.hasNext()) + _mainPS.printPrefixln("scd_rem_fifo_in " + cIt.next() + "_ins;"); + cIt = _remOutChannels.iterator(); + while (cIt.hasNext()) + _mainPS.printPrefixln("scd_rem_fifo_out " + cIt.next() + "_ins;"); + _mainPS.println(); + + // model constructor + _mainPS.printPrefixln("sc_application(sc_module_name name)"); + + // parameter of constructor (processes) + _mainPS.printPrefixln(": sc_module(name),"); + pIt = x.getProcessList().iterator(); + while (pIt.hasNext()) { + Process p = pIt.next(); + _mainPS.printPrefixln(p.getName() + "_ins(\"" + + p.getName() +"\"),"); + } + + // parameter of constructor (channels) + cIt = _channels.iterator(); + while (cIt.hasNext()) { + Channel c = _map.getPN().getChannel( cIt.next() ); + _mainPS.printPrefix(c.getName() + "_ins(\"" + + c.getName() +"\", " + + c.getSize()*c.getTokenSize() + + ")"); + if (cIt.hasNext()) + _mainPS.println(","); + else + _mainPS.println(); + } + _mainPS.printLeftBracket(); + + //construtor content + //build the network + cIt = _channels.iterator(); + while (cIt.hasNext()) + { + Channel c = _map.getPN().getChannel(cIt.next()); + c.accept( new HdsdModulePNVisitor(x, _mainPS) ); + } + _mainPS.println(); + + _mainPS.println(); + + _mainPS.printPrefixln("SC_THREAD(thread_init);"); + // init thread + _mainPS.printPrefixln("SC_THREAD(thread_sched);"); + + // declare concurrent non-terminating threads + pIt = x.getProcessList().iterator(); + while (pIt.hasNext()) { + _mainPS.printPrefixln("SC_THREAD(thread_" + + pIt.next().getName() + ");"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + //define scheduler thread + _mainPS.printPrefixln("void thread_init()"); + _mainPS.printLeftBracket(); + //init + pIt = x.getProcessList().iterator(); + while (pIt.hasNext()) { + Process p = pIt.next(); + _mainPS.printPrefixln(p.getName() + "_ins.initialize();"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + + //different scheduling algorithm can be put here + _mainPS.printPrefixln("void thread_sched()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (1)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("for (iter=eventList.begin(); iter != " + + "eventList.end(); ++iter)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("sc_event* e = (*iter);"); + _mainPS.printPrefixln("e->notify();"); + _mainPS.printRightBracket(); + _mainPS.printPrefixln("eventList.clear();"); + _mainPS.printPrefixln("wait(sched_event);"); + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + _mainPS.println(); + + //define threads + pIt = x.getProcessList().iterator(); + while (pIt.hasNext()) + pIt.next().accept( new HdsdModulePNVisitor(x, _mainPS) ); + + /* begin of profiling: initialization function. */ + /* - opens file */ + /* - writes a list of all channels with the connected ports to the file. */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("void initialize_profiler()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("if ((profiler_output_file = fopen(PROFILER_OUTPUT_FILENAME,\"w\"))==NULL)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("printf(\"Unable to open profiler output file. No profiling output is written.\\n\");"); + _mainPS.printPrefixln("return;"); + _mainPS.printRightBracket(); + //_mainPS.printPrefixln("printf(\"Profiling data is written to %s.\\n\", PROFILER_OUTPUT_FILENAME);"); + _mainPS.println(); + + cIt = _channels.iterator(); + while (cIt.hasNext()) { + String cName = cIt.next(); + Channel ch = _map.getPN().getChannel(cName); + Iterator poIt = ch.getPortList().iterator(); + String outputString = "fprintf(profiler_output_file, \"c " + ch.getName() + " " + ch.getSize(); + String outputStringAppendix = ""; + while (poIt.hasNext()) { + Port p = poIt.next(); + Port peerPort = p.getPeerPort(); + Resource peerResource = p.getPeerResource(); + + if (p.isOutPort()) { + // channel.out == process.in + String portAddr = null; + if ( x.hasProcess(peerResource.getName()) ) + portAddr = "&(" + peerResource.getName() + + "_ins.INPORT_" + peerPort.getBasename() + + peerPort.getName().replace( + peerPort.getBasename(), "").replaceAll( + "_([0-9]+)", "[$1]") + ")"; + else + portAddr = "0x00000000"; + outputString += " i " + peerResource.getName() + " %p";// + p.getPeerPort().getName(); + outputStringAppendix += "," + portAddr; + } else { + String portAddr = null; + if ( x.hasProcess(peerResource.getName()) ) + portAddr = "&(" + peerResource.getName() + + "_ins.OUTPORT_" + peerPort.getBasename() + + peerPort.getName().replace( + peerPort.getBasename(), "").replaceAll( + "_([0-9]+)", "[$1]") + ")"; + else + portAddr = "0x00000000"; + outputString += " o " + peerResource.getName() + " %p";// + p.getPeerPort().getName(); + outputStringAppendix += "," + portAddr; + } + } + outputString += "\\n\"" + outputStringAppendix + ");"; + _mainPS.printPrefixln(outputString); + } + _mainPS.printRightBracket(); + _mainPS.printPrefixln("#endif"); + _mainPS.println(); + /* end of profiling */ + + _mainPS.printRightBracket(); // end of class + _mainPS.println(";"); + + //create and run the simulator + _mainPS.printPrefixln("int sc_main (int argc, char *argv[])"); + _mainPS.printLeftBracket(); + + //create an instance of the application model + //remove potential whitespaces before using the process + //network name as a systemc identifier + _mainPS.printPrefixln("sc_application my_app_mdl(\"" + + x.getName().replaceAll(" ", "") + "\");"); + + /* begin of profiling: initialize the profiler */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("my_app_mdl.initialize_profiler();"); + _mainPS.printPrefixln("#endif"); + _mainPS.printPrefixln(); + /* end of profiling */ + + /* begin distributed simulation */ + if (UserInterface.getInstance().getDebugFlag()) + _mainPS.printPrefixln("scd_set_loglevel(SCD_DEBUG);"); + else + _mainPS.printPrefixln("scd_set_loglevel(SCD_INFO);"); + // simulator && master/slaves + Configuration opt = x.getCfg("master"); + if (opt != null && opt.getValue().equals("true")) // master + { + // simulator constructor + _mainPS.printPrefixln("scd_simulator sim(\"" + + x.getName() + "\", \"" + + x.getCfg("address").getValue() + "\", " + + x.getCfg("port").getValue() + ", SCD_MASTER);"); + // register slaves + Iterator iter = _map.getProcessorList().iterator(); + while (iter.hasNext()) + { + Processor p = iter.next(); + opt = p.getCfg("master"); + if ( !(opt != null && opt.getValue().equals("true")) ) + { + _mainPS.printPrefixln("sim.get_cont_man()." + + "register_slave(\"" + p.getName() + + "\");"); + } + } + } // end master + else // slave + { + // simulator constructor + _mainPS.printPrefixln("scd_simulator sim(\"" + + x.getName() + "\", \"" + + x.getCfg("address").getValue() + "\", " + + x.getCfg("port").getValue() + ", SCD_SLAVE);"); + // find and set master + Iterator iter = _map.getProcessorList().iterator(); + while (iter.hasNext()) + { + Processor p = iter.next(); + opt = p.getCfg("master"); + if (opt != null && opt.getValue().equals("true")) + { + _mainPS.printPrefixln("sim.get_cont_man().set_master(\"" + + p.getCfg("address").getValue() + "\", " + + p.getCfg("port").getValue() + ");"); + break; + } + } + } // end slave + + // register channels + // remote out channels + cIt = _remOutChannels.iterator(); + while (cIt.hasNext()) + { + String cName = cIt.next(); + Channel c = _map.getPN().getChannel(cName); + Iterator poIt = c.getPortList().iterator(); + Processor p = null; + while (poIt.hasNext()) + { + Port po = poIt.next(); + if (po.isOutPort()) + { + Process pr = _map.getProcess( + po.getPeerResource().getName() ); + p = pr.getProcessor(); + } + } + _mainPS.printPrefixln("sim.get_chan_man()." + + "register_channel(\"" + cName + "\","); + _mainPS.printPrefixln(" my_app_mdl." + cName + + "_ins, \"" + p.getCfg("address").getValue() + "\", " + + p.getCfg("port").getValue() + ");"); + } + // remote in channels + cIt = _remInChannels.iterator(); + while (cIt.hasNext()) + { + String cName = cIt.next(); + _mainPS.printPrefixln("sim.get_chan_man().register_channel(" + + "\"" + cName + "\","); + _mainPS.printPrefixln(" my_app_mdl." + cName + "_ins);"); + } + + // init & start + _mainPS.printPrefixln("bool ret = sim.init();"); + _mainPS.printPrefixln("if (ret)"); + _mainPS.prefixInc(); + _mainPS.printPrefixln("ret = sim.start();"); + _mainPS.prefixDec(); + _mainPS.printPrefixln(); + /* end distributed simulation */ + + /* begin of profiling: close the output file */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fclose(profiler_output_file);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("if (ret)"); + _mainPS.prefixInc(); + _mainPS.printPrefixln("return 0;"); + _mainPS.prefixDec(); + _mainPS.printPrefixln("else"); + _mainPS.prefixInc(); + _mainPS.printPrefixln("return 1;"); + _mainPS.prefixDec(); + + _mainPS.printRightBracket(); + + } + catch (Exception e) { + System.out.println("HdsdModuleVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + + /** + * Retrieves all channels that have an endpoint on the specified + * processor and fills them into the channel sets. + * @param x the processor to get the channels for + */ + private void _getChannels(Processor x) + { + Iterator iter = _map.getPN().getChannelList().iterator(); + + while (iter.hasNext()) + { + Channel c = iter.next(); + + boolean isIn = false; + boolean isOut = false; + + /* check if the in/out endpoint of this channel is on this + * processor + */ + Iterator poIt = c.getPortList().iterator(); + while (poIt.hasNext()) + { + Port p = poIt.next(); + if (x.hasProcess(p.getPeerResource().getName())) + { + if (p.isInPort()) // inPort of channel = outport + isOut = true; + if (p.isOutPort()) // outPort of channel = inport + isIn = true; + } + } + + // add name to channel lists + if (isIn && isOut) + { + _channels.add(c.getName()); + _locChannels.add(c.getName()); + } + else if (isIn && !isOut) + { + _channels.add(c.getName()); + _remInChannels.add(c.getName()); + } + else if (!isIn && isOut) + { + _channels.add(c.getName()); + _remOutChannels.add(c.getName()); + } + } + } // _getChannels() + + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected Mapping _map = null; + + protected Set _channels = null; + protected Set _remInChannels = null; + protected Set _remOutChannels = null; + protected Set _locChannels = null; +} diff --git a/dol/src/dol/visitor/hdsd/HdsdModulePNVisitor.java b/dol/src/dol/visitor/hdsd/HdsdModulePNVisitor.java new file mode 100644 index 0000000..466dba3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdModulePNVisitor.java @@ -0,0 +1,113 @@ +package dol.visitor.hdsd; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.Resource; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * Visitor that generates the main program thread of a process + * and the process network in the constructor. + */ +public class HdsdModulePNVisitor extends PNVisitor { + + /** + * Constructor. + */ + public HdsdModulePNVisitor(Processor processor, CodePrintStream stream) { + _processor = processor; + _mainPS = stream; + } + + /** + * Generates the thread for this process. + * + * @param x process that needs to be rendered + */ + public void visitComponent(Process x) { + _mainPS.printPrefixln("void thread_" + x.getName() + "()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (!" + x.getName() + + "_ins.isDetached())"); + _mainPS.printLeftBracket(); + + /* begin of profiling: start event */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fprintf(profiler_output_file, \"%u "+ x.getName() + " started.\\n\", profiler_event_counter++);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln(x.getName() + "_ins.fire();"); + + /* begin of profiling: stop event */ + _mainPS.printPrefixln("#ifdef INCLUDE_PROFILER"); + _mainPS.printPrefixln("if (profiler_output_file != NULL) fprintf(profiler_output_file, \"%u "+ x.getName() + " stopped.\\n\", profiler_event_counter++);"); + _mainPS.printPrefixln("#endif"); + /* end of profiling */ + + _mainPS.printPrefixln("eventList.push_back(&" + x.getName() + + "_event);"); + _mainPS.printPrefixln("sched_event.notify(SC_ZERO_TIME);"); + _mainPS.printPrefixln("wait(" + x.getName() + "_event);"); + + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + } + + /** + * Generates the process network. This is the channel content + * in the model constructor. + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + for (Port p : x.getPortList()) { + Port peerPort = (Port)(p.getPeerPort()); + Resource peerResource = p.getPeerResource(); + + if (!_processor.hasProcess(peerResource.getName())) + continue; + + if (peerPort.getRange() != null) { + if (p.isOutPort()) { + _mainPS.printPrefixln(peerResource.getName() + + "_ins.INPORT_" + + peerPort.getBasename() + + peerPort.getName().replace( + peerPort.getBasename(), "").replaceAll( + "_([0-9]+)", "[$1]") + "(" + x.getName() + + "_ins);"); + } + else if (p.isInPort()) { + _mainPS.printPrefixln(peerResource.getName() + + "_ins.OUTPORT_" + + peerPort.getBasename() + + peerPort.getName().replace( + peerPort.getBasename(), "").replaceAll( + "_([0-9]+)", "[$1]") + "(" + x.getName() + + "_ins);"); + } + } + else { + if (p.isOutPort()) { + _mainPS.printPrefixln(peerResource.getName() + + "_ins.INPORT_" + peerPort.getName() + + "(" + x.getName() + "_ins);"); + } + else if (p.isInPort()) { + _mainPS.printPrefixln(peerResource.getName() + + "_ins.OUTPORT_" + peerPort.getName() + + "(" + x.getName() + "_ins);"); + } + } + } + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected Mapping _map = null; + protected Processor _processor = null; +} diff --git a/dol/src/dol/visitor/hdsd/HdsdModuleVisitor.java b/dol/src/dol/visitor/hdsd/HdsdModuleVisitor.java new file mode 100644 index 0000000..e7b316f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdModuleVisitor.java @@ -0,0 +1,38 @@ +package dol.visitor.hdsd; + +import java.util.Iterator; + +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.visitor.MapVisitor; + +/** + * Visitor that generates the main programs of distributed simulators. + */ +public class HdsdModuleVisitor extends MapVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public HdsdModuleVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x mapping that needs to be rendered + */ + public void visitComponent(Mapping x) { + Iterator iter; + iter = x.getProcessorList().iterator(); + while (iter.hasNext()) + { + Processor p = iter.next(); + p.accept(new HdsdModuleArchVisitor(x, _dir)); + } + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/hdsd/HdsdProcessVisitor.java b/dol/src/dol/visitor/hdsd/HdsdProcessVisitor.java new file mode 100644 index 0000000..2ba1044 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdProcessVisitor.java @@ -0,0 +1,364 @@ +package dol.visitor.hdsd; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * Visitor that is used to generate + * a wrapper class for a process: process_wrapper.[h/cpp]. + */ +public class HdsdProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public HdsdProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param p process that needs to be rendered + */ + public void visitComponent(Process p) { + try { + _createCppFile(p); + _createHeaderFile(p); + _adaptSources(p); + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _adaptSources(Process p) throws IOException { + Sed sed = new Sed(); + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(). + replaceAll("(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (port.isOutPort()) { + sed.sed(processHeaderFile, + "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?", + "$1 " + "&((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->OUTPORT_" + + port.getBasename() + ")"); + + } + else if (port.isInPort()) { + sed.sed(processHeaderFile, + "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?", + "$1 " + "&((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->INPORT_" + + port.getBasename() + ")"); + } + } + } + } + + /** + * + */ + protected void _createCppFile(Process p) throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#include \"" + p.getBasename() + + "_wrapper.h\""); + ps.printPrefixln(); + + //define the write/read + if (p.hasOutPorts()) { + ps.printPrefixln("static inline int DOL_write(void *port, " + + "void *buf, int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("sc_port *write_port = static_cast" + + " *>(port);"); + ps.printPrefixln("char *str = static_cast(buf);"); + + /* begin of profiling: write event */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))" + "->addToProfile(\"w\", port, len);"); + ps.printPrefixln("#endif"); + /* end of profiling */ + + ps.printPrefixln("while (len-- > 0) "); + ps.printPrefixln(" (*write_port)->write(*str++);"); + ps.printRightBracket(); + ps.printPrefixln(); + ps.printPrefixln("static inline int DOL_wtest(void *port, " + + "int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("sc_port *write_port = static_cast" + + " *>(port);"); + ps.printPrefixln("return (*write_port)->wtest(len);"); + ps.printRightBracket(); + ps.printPrefixln(); + } + if (p.hasInPorts()) { + ps.printPrefixln("static inline int DOL_read(void *port, " + + "void *buf, int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("sc_port *read_port = static_cast" + + " *>(port);"); + ps.printPrefixln("char *str = static_cast(buf);"); + + /* begin of profiling: read event */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))" + "->addToProfile(\"r\", port, len);"); + ps.printPrefixln("#endif"); + /* end of profiling */ + + ps.printPrefixln("while (len-- > 0) "); + ps.printPrefixln(" (*read_port)->read(*str++);"); + ps.printRightBracket(); + ps.printPrefixln(); + ps.printPrefixln("static inline int DOL_rtest(void *port, " + + "int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("sc_port *read_port = static_cast" + + " *>(port);"); + ps.printPrefixln("return (*read_port)->rtest(len);"); + ps.printRightBracket(); + } + ps.printPrefixln(); + ps.printPrefixln("static inline int DOL_detach(DOLProcess *p)"); + ps.printLeftBracket(); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))" + "->setDetached();"); + ps.printRightBracket(); + ps.printPrefixln(); + + ps.printPrefixln("#define GETINDEX(dimension) \\"); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->_processIndex[dimension]"); + ps.printPrefixln(); + //include c file + for (SourceCode sr : p.getSrcList()) { + ps.printPrefixln("#include \"" + sr.getLocality() + "\""); + } + ps.printPrefixln(); + + /* begin of profiling: function that adds an entry to the profile */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("void " + p.getBasename() + "_wrapper::addToProfile(const char *evnt, void *port, int length)"); + ps.printLeftBracket(); + ps.printPrefixln("if (profiler_output_file != NULL) fprintf(profiler_output_file, \"%u %s %s %p %d\\n\", profiler_event_counter++, _uniqueName, evnt, port, length);"); + ps.printRightBracket(); + ps.printPrefixln("#endif"); + /* end of profiling */ + + ps.printPrefixln("void " + p.getBasename() + + "_wrapper::setDetached() { _detached = 1; }"); + ps.printPrefixln("int " + p.getBasename() + + "_wrapper::isDetached() { return _detached; }"); + ps.printPrefixln(); + + //constructor + ps.printPrefixln(p.getBasename() + "_wrapper::" + p.getBasename() + + "_wrapper(sc_module_name name=sc_gen_unique_name(\"" + + p.getBasename() + "\" )) : sc_module(name), _process(" + + p.getBasename() + ")" + ", _detached(0)"); + ps.printLeftBracket(); + ps.printPrefixln("struct _local_states *_state = " + + "new struct _local_states;"); + ps.printPrefixln("memcpy(_state, " + p.getBasename() + + ".local, sizeof(struct _local_states));"); + ps.printPrefixln("_process.local = _state;"); + //ps.printPrefixln("sprintf(_process.local->id, name);"); + ps.printPrefixln("_process.wptr = this;"); + ps.printPrefixln(); + ps.printPrefixln("char buffer[255];"); + ps.printPrefixln("sprintf(buffer, name);"); + + /* begin of profiling: save unique name for writing it to + the profile later */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("sprintf(_uniqueName, name);"); + ps.printPrefixln("#endif"); + /* end of profiling */ + + ps.printPrefixln("for (int i = 0; i < 4; i++)"); + ps.printPrefixln(" _processIndex[i] = " + + "getIndex(buffer, \"_\", i);"); + + ps.printPrefixln(); + ps.printRightBracket(); + + ps.printPrefixln(); + ps.printPrefixln("void " + p.getBasename() + + "_wrapper::initialize()"); + ps.printLeftBracket(); + ps.printPrefixln("_process.init(&_process);"); + ps.printRightBracket(); + ps.printPrefixln(); + ps.printPrefixln("int " + p.getBasename() + "_wrapper::fire()"); + ps.printLeftBracket(); + ps.printPrefixln(); + ps.printPrefixln("return _process.fire(&_process);"); + ps.printRightBracket(); + } + + protected void _createHeaderFile(Process p) + throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#ifndef " + p.getBasename() + "_WRAPPER_H"); + ps.printPrefixln("#define " + p.getBasename() + "_WRAPPER_H"); + ps.printPrefixln(); + ps.printPrefixln("#include \"systemc.h\""); + ps.printPrefixln(); + ps.printPrefixln("#include \"dol_sched_if.h\""); + ps.printPrefixln("#include \"simple_fifo.h\""); + ps.printPrefixln(); + ps.printPrefixln("#include "); + ps.printPrefixln(); + + /* begin of profiling: externally defined global variables */ + ps.println(); + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("extern FILE *profiler_output_file;"); + ps.printPrefixln("extern unsigned int profiler_event_counter;"); + ps.printPrefixln("#endif"); + ps.printPrefixln(); + /* end of profiling */ + + ps.printPrefixln("class " + p.getBasename() + + "_wrapper : virtual public dol_sched_if, " + + "public sc_module"); + ps.printLeftBracket(); + ps.printPrefixln("public:"); + + + Vector portList = new Vector(); + for (Port port : p.getPortList()) { + String basename = port.getBasename(); + + if (!portList.contains(basename)) { + portList.add(basename); + + if (!port.getRange().equals("")) { + if (port.isOutPort()) { + ps.printPrefixln("sc_port OUTPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];"); + } + else if (port.isInPort()) { + ps.printPrefixln("sc_port INPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];"); + } + } + else { + if (port.isOutPort()) { + ps.printPrefixln("sc_port OUTPORT_" + + port.getName() + ";"); + } + else if (port.isInPort()) { + ps.printPrefixln("sc_port INPORT_" + + port.getName() + ";"); + } + } + } + } + ps.printPrefixln("int _processIndex[4];"); + + /* begin of profiling: name variable declaration */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("char _uniqueName[255];"); + ps.printPrefixln("#endif"); + /* end of profiling */ + + ps.printPrefixln(); + ps.printPrefixln("" + p.getBasename() + + "_wrapper(sc_module_name name);"); + ps.printPrefixln(); + ps.printPrefixln("~" + p.getBasename() + "_wrapper() {}"); + ps.printPrefixln(); + + /* begin of profiling: addtoprofile member function */ + ps.printPrefixln("#ifdef INCLUDE_PROFILER"); + ps.printPrefixln("void addToProfile(const char *, void *, int);"); + ps.printPrefixln("#endif"); + ps.printPrefixln(); + /* end of profiling */ + + ps.printPrefixln("// DOL scheduler interface"); + ps.printPrefixln("void initialize();"); + ps.printPrefixln("int fire();"); + ps.printPrefixln("void setDetached();"); + ps.printPrefixln("int isDetached();"); + ps.printPrefixln(); + ps.printPrefixln(); + ps.printPrefixln("protected:"); + ps.printPrefixln(); + ps.printPrefixln("private:"); + ps.printPrefixln("" + p.getBasename() + "_wrapper( const " + + p.getBasename() + "_wrapper& );"); + ps.printPrefixln("" + p.getBasename() + "_wrapper& operator = " + + "( const " + p.getBasename() + "_wrapper& );"); + ps.printPrefixln("DOLProcess _process;"); + ps.printPrefixln("int _detached;"); + ps.printRightBracket(); + ps.printPrefixln(";"); + ps.printPrefixln("#endif"); + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/hdsd/HdsdScriptVisitor.java b/dol/src/dol/visitor/hdsd/HdsdScriptVisitor.java new file mode 100644 index 0000000..322cb77 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/HdsdScriptVisitor.java @@ -0,0 +1,376 @@ +package dol.visitor.hdsd; + + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; + +import dol.datamodel.architecture.Configuration; +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.main.UserInterface; +import dol.util.CodePrintStream; +import dol.visitor.MapVisitor; + +/** + * Visitor that is used to generate a shell script to + * distribute, run, monitor, profile and clean up a distributed + * simulation. + */ +public class HdsdScriptVisitor extends MapVisitor { + + /** + * Constructor. + * + * @param dir path of the shell script + */ + public HdsdScriptVisitor(String dir) { + _dir = dir; + } + + /** + * Create the shell script for the given mapping. + * + * @param x mapping that needs to be rendered. + */ + public void visitComponent(Mapping x) { + try { + String filename = _dir + "/" + "scd.sh"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + List pList = x.getProcessorList(); + int numProcessors = pList.size(); + + // arrays to hold strings for generation + String[] hNames = new String[numProcessors]; + String[] hAddrs = new String[numProcessors]; + String[] hDirs = new String[numProcessors]; + String[] hUsers = new String[numProcessors]; + + // get processor information and fill to string arrays + int numSlaves = 0; + Iterator iter = pList.iterator(); + while (iter.hasNext()) + { + Processor p = iter.next(); + int pIdx = numSlaves; + boolean master = false; + Configuration c = p.getCfg("master"); + if (c != null && c.getValue().equals("true")) + master = true; + + // if it is the master, it will be the last processor + if (master) + pIdx = numProcessors-1; + + // fill information to string arrays + hNames[pIdx] = p.getName(); + hAddrs[pIdx] = p.getCfg("address").getValue(); + c = p.getCfg("basedir"); + if (c != null) + { + String dir = c.getValue(); + // remove tailing '/' + if ( dir.endsWith("/") ) + dir = dir.replaceAll("/" + "$", ""); + if (dir.equals("")) + hDirs[pIdx] = hNames[pIdx]; + else + hDirs[pIdx] = dir + "/" + hNames[pIdx]; + } + else + hDirs[pIdx] = hNames[pIdx]; + c = p.getCfg("username"); + if (c != null) + hUsers[pIdx] = c.getValue() + "@"; + else + hUsers[pIdx] = ""; + + if (!master) + numSlaves++; + } // end get and fill information + + /* generate script */ + // head + ps.println("#!/bin/bash"); + ps.println(); + ps.println("SCRIPTNAME=\"`echo $0 | sed 's/.*\\///'`\""); + ps.println(); + + // host configuration + ps.println("### host configuration"); + ps.print("NAME=( "); + for (int i=0; i scd_profilerout.txt 2>&1"); + ps.printPrefixln("if [ $? -ne 0 ]; then"); + ps.prefixInc(); + ps.printPrefixln("echo"); + ps.printPrefixln("echo \"Profiling ${NAME[$1]} failed!\""); + ps.printPrefixln("echo \"Check scd_tmp_profilerout.txt for more information.\""); + ps.printPrefixln("echo"); + ps.printPrefixln("rm scd_tmp_profiled.xml"); + ps.printPrefixln("exit 1"); + ps.prefixDec(); + ps.printPrefixln("fi"); + ps.printPrefixln("rm scd_profilerout.txt"); + ps.printPrefixln("mv scd_tmp_profiled_annotated.xml scd_tmp_profiled.xml"); + ps.printPrefixln("rm profile_${NAME[$1]}.txt"); + ps.prefixDec(); + ps.println("}"); + ps.println(); + + // main programm start + ps.println("### main program"); + ps.println(); + ps.println("# require at least one argument"); + ps.println("if [ ! $# -ge 1 ]; then"); + ps.prefixInc(); + ps.printPrefixln("echo"); + ps.printPrefixln("echo \"Error: At least one argument is required!\""); + ps.printPrefixln("usage"); + ps.printPrefixln("exit 1"); + ps.prefixDec(); + ps.println("fi"); + ps.println(); + ps.println("# switch on operation"); + ps.println("case \"$1\" in"); + + // distribute binaries to simulators + ps.println("\"distribute\")"); + ps.prefixInc(); + ps.printPrefixln("echo \"Distributing binaries to simulators...\""); + ps.printPrefixln("# prepare directories"); + for (int i=0; i stdout_${NAME[" + i + "]}.txt 2>&1 &"); + ps.printPrefixln("ssh ${USER[" + (numProcessors-1) + + "]}${ADDR[" + (numProcessors-1) + + "]} \"cd ${DIR[" + (numProcessors-1) + + "]}; ./scd_${NAME[" + (numProcessors-1) + + "]}\" 2>&1 | tee stdout_${NAME[" + + (numProcessors-1) + "]}.txt"); + ps.prefixDec(); + ps.println(";;"); + + // check if simulators are running + ps.println("\"check\")"); + ps.prefixInc(); + ps.printPrefixln("echo \"Checking which simulators are active...\""); + for (int i=0; i /dev/null && echo ${NAME[" + i + "]} is active || echo ${NAME[" + i + "]} is not active\""); + ps.prefixDec(); + ps.println(";;"); + + // kill all simulators + ps.println("\"kill\")"); + ps.prefixInc(); + ps.printPrefixln("echo \"Killing all simulators...\""); + for (int i=0; i /dev/null"); + ps.printPrefixln("if [ $? -ne 0 ]; then"); + ps.prefixInc(); + ps.printPrefixln("echo"); + ps.printPrefixln("echo \"Error: Illegal file extension: required .xml!\""); + ps.printPrefixln("echo"); + ps.printPrefixln("exit 1"); + ps.prefixDec(); + ps.printPrefixln("fi"); + ps.println(); + ps.printPrefixln("PFILE=`echo $2 | sed 's/.*\\///' | sed 's/\\.xml$/_annotated.xml/'`"); + ps.printPrefixln("echo \"Profiling into ${PFILE}...\""); + ps.println(); + ps.printPrefixln("# check that the DOL is found and the classpath set correctly"); + ps.printPrefixln("java dol.main.Main > /dev/null 2>&1"); + ps.printPrefixln("if [ $? -eq 1 ]; then"); + ps.prefixInc(); + ps.printPrefixln("echo"); + ps.printPrefixln("echo \"DOL framework not found!\""); + ps.printPrefixln("echo"); + ps.printPrefixln("exit 1"); + ps.prefixDec(); + ps.printPrefixln("fi"); + ps.printPrefixln("cp \"$2\" scd_tmp_profiled.xml"); + ps.println(); + for (int i=0; i +#include +#include + +#ifdef __DOL_ETHZ_GEN__ +#include +using sc_core::sc_port; +#endif + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * Define the DOL process handler scheme. + * - Local variables are defined in structure LocalState. Local + * variables may vary from different processes. + * - The ProcessInit function pointer points to a function which + * initializes a process. + * - The ProcessFire function pointer points to a function which + * performs the actual computation. The communication between + * processes is inside the ProcessFire function. + * - The WPTR is a placeholder for callback. One can just + * leave it blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + + +//macros to deal with iterated ports +/** + ****************************************************** + * The HdS code is needed to replace the current ETHZ * + * implementation. * + ****************************************************** + * + * Macro to create a variable to store a port name. + * + * @param name name of the variable + */ +#ifdef __DOL_ETHZ_GEN__ +#define CREATEPORTVAR(name) sc_port *name +#else +#define CREATEPORTVAR(name) // Hds Code +#endif + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort(&port, base, number_of_indices, index_range_pairs) + +int getIndex(const char* string, char* tokens, int indexNumber); + +/* + ****************************************************** + * The HdS code is needed to replace the current ETHZ * + * implementation. * + ****************************************************** + */ +#ifdef __DOL_ETHZ_GEN__ +template +sc_port *createPort(sc_port **port, + void *base, + int number_of_indices, + int index0, int range0) { + *port = &((static_cast *>(base))[index0]); + return &((static_cast *>(base))[index0]); +} + +template +sc_port *createPort(sc_port **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1) { + *port = &((static_cast *>(base))[ + index0 * range1 + index1]); + return &((static_cast *>(base))[ + index0 * range1 + index1]); +} + +template +sc_port *createPort(sc_port **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2) { + *port = &((static_cast *>(base))[ + index0 * range1 * range2 + index1 * range0 + index2]); + return &((static_cast *>(base))[ + index0 * range1 * range2 + index1 * range0 + index2]); +} + +template +sc_port *createPort(sc_port **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3) { + *port = &((static_cast *>(base))[ + index0 * range1 * range2 * range3 + + index1 * range2 * range3 + index2 * range3 + index3]); + return &((static_cast *>(base))[ + index0 * range1 * range2 * range3 + + index1 * range2 * range3 + index2 * range3 + index3]); +} +#else +#define createPort() //HdS code +#endif + +#endif diff --git a/dol/src/dol/visitor/hdsd/lib/dol_sched_if.h b/dol/src/dol/visitor/hdsd/lib/dol_sched_if.h new file mode 100644 index 0000000..d861b1e --- /dev/null +++ b/dol/src/dol/visitor/hdsd/lib/dol_sched_if.h @@ -0,0 +1,43 @@ +/************************************************************************** + dol_sched_if.h + + Scheduler interface for a DOL process + + (c) 2006 by Alexander Maxiaguine + + Computer Engineering and Networks Laboratory, TIK + Swiss Federal Institute of Technology, ETHZ Zurich + Switzerland + +**************************************************************************/ + +/************************************************************************** + Change Log: + + 14.03.06 -- creation + +**************************************************************************/ + +#ifndef DOL_SCHED_IF_H +#define DOL_SCHED_IF_H + +class dol_sched_if +{ + +public: + virtual void initialize() = 0; + virtual int fire() = 0; + + +protected: + dol_sched_if() {} + + +private: + + // disabled + dol_sched_if( const dol_sched_if& ); + dol_sched_if& operator = ( const dol_sched_if& ); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/lib/simple_fifo.h b/dol/src/dol/visitor/hdsd/lib/simple_fifo.h new file mode 100644 index 0000000..b070b8b --- /dev/null +++ b/dol/src/dol/visitor/hdsd/lib/simple_fifo.h @@ -0,0 +1,186 @@ +/***************************************************************************** + + The following code is derived, directly or indirectly, from the SystemC + source code Copyright (c) 1996-2004 by all Contributors. + All Rights reserved. + + The contents of this file are subject to the restrictions and limitations + set forth in the SystemC Open Source License Version 2.4 (the "License"); + You may not use this file except in compliance with such restrictions and + limitations. You may obtain instructions on how to receive a copy of the + License at http://www.systemc.org/. Software distributed by Contributors + under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF + ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + *****************************************************************************/ + +/***************************************************************************** + + simple_fifo.cpp -- Simple SystemC 2.0 producer/consumer example. + + From "An Introduction to System Level Modeling in + SystemC 2.0". By Stuart Swan, Cadence Design Systems. + Available at www.systemc.org + + Original Author: Stuart Swan, Cadence Design Systems, 2001-06-18 + + *****************************************************************************/ + +/***************************************************************************** + + MODIFICATION LOG - modifiers, enter your name, affiliation, date and + changes you are making here. + + Name, Affiliation, Date: + Description of Modification: + + *****************************************************************************/ + +#ifndef _SIMPLE_FIFO_H_ +#define _SIMPLE_FIFO_H_ + +#include +using sc_core::sc_interface; +using sc_core::sc_channel; +using sc_core::sc_event; +using sc_core::sc_module_name; + +class write_if : virtual public sc_interface +{ + public: + virtual void write(char) = 0; + virtual void reset() = 0; + virtual int wtest(int) = 0; +}; + +class read_if : virtual public sc_interface +{ + public: + virtual void read(char &) = 0; + virtual int num_available() = 0; + virtual int rtest(int) = 0; +}; + +class fifo : public sc_channel, public write_if, public read_if +{ + public: + fifo(sc_module_name name, int size) + : sc_channel(name), num_elements(0), first(0), max(size) { + data = new char[size]; + } + + void write(char c) { + if (num_elements == max) + wait(read_event); + + data[(first + num_elements) % max] = c; + ++ num_elements; + write_event.notify(); + } + + void read(char &c){ + if (num_elements == 0) + wait(write_event); + + c = data[first]; + -- num_elements; + first = (first + 1) % max; + read_event.notify(); + } + + int rtest(int size) { return (size <= num_elements) ? 1 : 0; } + int wtest(int size) { return (size <= max - num_elements) ? 1 : 0; } + + + void reset() { num_elements = first = 0; } + + int num_available() { return num_elements;} + + private: + int max; + char *data; + int num_elements, first; + sc_event write_event, read_event; +}; + + +#endif + +/* +class producer : public sc_module +{ + public: + sc_port out; + + SC_HAS_PROCESS(producer); + + producer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + const char *str = + "Visit www.systemc.org and see what SystemC can do for you today!\n"; + + while (*str) + out->write(*str++); + } +}; + +class consumer : public sc_module +{ + public: + sc_port in; + + SC_HAS_PROCESS(consumer); + + consumer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + char c; + cout << endl << endl; + + while (true) { + in->read(c); + cout << c << flush; + + if (in->num_available() == 1) + cout << "<1>" << flush; + if (in->num_available() == 9) + cout << "<9>" << flush; + } + } +}; + +class top : public sc_module +{ + public: + fifo *fifo_inst; + producer *prod_inst; + consumer *cons_inst; + + top(sc_module_name name) : sc_module(name) + { + fifo_inst = new fifo("Fifo1"); + + prod_inst = new producer("Producer1"); + prod_inst->out(*fifo_inst); + + cons_inst = new consumer("Consumer1"); + cons_inst->in(*fifo_inst); + } +}; + +int sc_main (int argc , char *argv[]) { + top top1("Top1"); + sc_start(-1); + return 0; +} +*/ diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.cpp new file mode 100644 index 0000000..68fc296 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.cpp @@ -0,0 +1,30 @@ +#include "fsm/scd_cont_fsm.h" + +#include "scd_logging.h" +#include "scd_cont_state.h" + + +void scd_cont_fsm::set_state(scd_cont_state& state) +{ + _state = &state; + scd_debug(_name + ": [" + _state->get_name() +"]"); +} + + +void scd_cont_fsm::save_state() +{ + _hist_state = _state; +} + + +void scd_cont_fsm::save_state(scd_cont_state& state) +{ + _hist_state = &state; +} + + +void scd_cont_fsm::load_state() +{ + _state = _hist_state; + scd_debug(_name + ": [" + _state->get_name() +"]"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.h new file mode 100644 index 0000000..f5d2eb4 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm.h @@ -0,0 +1,54 @@ +#ifndef SCD_CONT_FSM_H +#define SCD_CONT_FSM_H + +#include + +/* forward declaration */ +class scd_cont_state; + + +/** + * FSM base class. Holds the current and the history state and allows + * state transitions. Transitions are logged in debug loglevel. + */ +class scd_cont_fsm +{ + +public: + /** + * Constructor. + * \param name the name of the state machine + */ + scd_cont_fsm(const std::string& name): _name(name) {} + + virtual ~scd_cont_fsm() {} + + /** + * Sets a new state. This is a state transition. + */ + void set_state(scd_cont_state& state); + + /** + * Saves the current state as the history state. This is not a state + * transition. + */ + void save_state(); + + /** + * Saves a specified state as the history state. This is not a state + * transition. + */ + void save_state(scd_cont_state& state); + + /** + * Restores the saved history state. This is a state transition. + */ + void load_state(); + +protected: + scd_cont_state* _state; + scd_cont_state* _hist_state; + std::string _name; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm_if.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm_if.h new file mode 100644 index 0000000..b8897a5 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_fsm_if.h @@ -0,0 +1,80 @@ +#ifndef SCD_CONT_FSM_IF_H +#define SCD_CONT_FSM_IF_H + +#include "systemc" + + +/** + * Public interface for control manager state machines (master, slave, slave + * wrapper). + */ +class scd_cont_fsm_if +{ +public: + virtual ~scd_cont_fsm_if() {} + + /** + * Signalize the FSM that the simulation is idle and the next event + * is in a specific amount of time (relative). + * \param time time of next event relative to current time + */ + virtual void set_idle(const sc_core::sc_time& time) = 0; + + /** + * Signalizes the FSM that the simulation can be run. + */ + virtual void set_busy() = 0; + + /** + * Signalizes the FSM that the simulation on this simulator has finished. + * No more events exist right now. + */ + virtual void set_done() = 0; + + /** + * Signalizes the FSM that an error has occured and the simulation + * has to be brought down. + */ + virtual void set_fail() = 0; + + /** + * Drives the control FSM. Shall be called repeatedly (i.e. from the + * main loop). + */ + virtual void process() = 0; + + /** + * Indicates if the FSM is initiated and still working. + * \return true if the state is not init, terminated or failed + */ + virtual bool active() const = 0; + + /** + * Indicates if another step should be simulated (i.e. the FSM is in + * the busy state). + * \return true if the FSM is in busy state + */ + virtual bool busy() const = 0; + + /** + * Indicates if the FSM is in the failed state. + * \return false if an error occured and the FSM stopped working + */ + virtual bool failed() const = 0; + + /** + * Indicates if simulation time has to be advanced (i.e. the FSM + * is in the time state). If this is the case, the time step + * shall be obtained by calling get_time_step(). + * \return true if the time has to be advanced + */ + virtual bool advance_time() const = 0; + + /** + * Returns the time step by which the simulation time has to be advanced. + * Shall only be called in time state. + */ + virtual const sc_core::sc_time& get_time_step() = 0; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_state.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_state.h new file mode 100644 index 0000000..82e09d3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_state.h @@ -0,0 +1,38 @@ +#ifndef SCD_CONT_STATE_H +#define SCD_CONT_STATE_H + +#include + +#include "scd_simulator.h" +#include "fsm/scd_cont_fsm_if.h" + + +/* forward declaration */ +class scd_simulator; + + +/** + * Base class of all FSM states. + */ +class scd_cont_state : public scd_cont_fsm_if +{ +public: + /** + * Constructor. + * \param sim the simulation environment + */ + scd_cont_state(scd_simulator& sim): _sim(sim) {} + + virtual ~scd_cont_state() {} + + /** + * Returns the name of the state. + */ + const std::string& get_name() const { return _name; } + +protected: + scd_simulator& _sim; + std::string _name; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_wrapper_if.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_wrapper_if.h new file mode 100644 index 0000000..12d6f50 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_cont_wrapper_if.h @@ -0,0 +1,80 @@ +#ifndef SCD_CONT_WRAPPER_IF_H +#define SCD_CONT_WRAPPER_IF_H + +#include "systemc" + + +/** + * Extends the public interface of the slave wrapper FSM. + */ +class scd_cont_wrapper_if +{ +public: + /** + * Sends a time_req command to the slave. + */ + virtual void send_time_req() = 0; + + /** + * Sends a time_nack command to the slave. + */ + virtual void send_time_nack() = 0; + + /** + * Sends a time command to the slave. + */ + virtual void send_time(const sc_core::sc_time& time) = 0; + + /** + * Sends a term_req command to the slave. + */ + virtual void send_term_req() = 0; + + /** + * Sends a term_nack command to the slave. + */ + virtual void send_term_nack() = 0; + + /** + * Sends a term command to the slave. + */ + virtual void send_term() = 0; + + /** + * Inidcates if the slave is in term_req state. + * \return true if the slave seems to be in time_req state + */ + virtual bool time_req() const = 0; + + /** + * Inidcates if the slave is in time_ack state. + * \return true if the slave seems to be in time_ack state + */ + virtual bool time_ack() const = 0; + + /** + * Inidcates if the slave is in term_req state. + * \return true if the slave seems to be in term_req state + */ + virtual bool term_req() const = 0; + + /** + * Inidcates if the slave is in term_ack state. + * \return true if the slave seems to be in term_ack state + */ + virtual bool term_ack() const = 0; + + /** + * Inidcates if the slave is in idle state. + * \return true if the slave seems to be in idle state + */ + virtual bool idle() const = 0; + + /** + * Indicates if the slave is in done state. + * \return true if the slave seems to be in done state + */ + virtual bool done() const = 0; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.cpp new file mode 100644 index 0000000..a0175a3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.cpp @@ -0,0 +1,103 @@ +#include "fsm/scd_stm_base.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" +#include "scd_command.h" +#include "scd_cont_man.h" + + +scd_stm_base::scd_stm_base(scd_simulator& sim, scd_cont_man_master& fsm): + scd_cont_state(sim), _fsm(fsm), _slaves(fsm._slaves), + _time_step(fsm._time_step), + _st_init(fsm._st_init), _st_busy(fsm._st_busy), _st_idle(fsm._st_idle), + _st_done(fsm._st_done), _st_time_req(fsm._st_time_req), + _st_time(fsm._st_time), _st_term_req(fsm._st_term_req), + _st_terminate(fsm._st_terminate), _st_terminated(fsm._st_terminated), + _st_fail(fsm._st_fail), _st_failed(fsm._st_failed) +{ +} + + +void scd_stm_base::set_fail() +{ + std::list::iterator it; + + for (it = _slaves.begin(); it != _slaves.end(); it++) + { + (*it)->set_fail(); + } + + _fsm.set_state(_st_fail); +} // set_fail() + + +void scd_stm_base::process() +{ + // check for failed slaves and react + if (!_check_slaves()) + return; +} + + +bool scd_stm_base::active() const { return true; } + + +bool scd_stm_base::busy() const { return false; } + + +bool scd_stm_base::failed() const { return false; } + + +bool scd_stm_base::advance_time() const { return false; } + + +const sc_core::sc_time& scd_stm_base::get_time_step() +{ + scd_error("illegal call to get_time_step()"); + throw scd_exception("illegal call"); +} + + +bool scd_stm_base::_check_slaves() +{ + + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if ( (*iter)->failed() ) + { + scd_error("slave \"" + (*iter)->get_name() + "\" failed"); + set_fail(); + return false; + } + } + + return true; + +} // _check_slaves() + + +bool scd_stm_base::_some_slaves_active() +{ + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if ( (*iter)->active() ) + return true; + } + + return false; + +} // _some_slaves_active() + + +void scd_stm_base::_close_slaves() +{ + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->close(); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.h new file mode 100644 index 0000000..6b8463f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_base.h @@ -0,0 +1,64 @@ +#ifndef SCD_STM_BASE_H +#define SCD_STM_BASE_H + +#include + +#include "scd_simulator.h" +#include "scd_cont_slave_wrapper.h" +#include "fsm/scd_cont_state.h" + + +/* forward declaration */ +class scd_cont_man_master; + + +/** + * Base class for all control master states. + */ +class scd_stm_base : public scd_cont_state +{ +public: + /** + * Constructor. + * \param sim the simulation environment + * \param fsm the FSM of this state + */ + scd_stm_base(scd_simulator& sim, scd_cont_man_master& fsm); + + virtual ~scd_stm_base() {} + + /* scd_cont_fsm_if */ + void set_busy() {} + void set_idle(const sc_core::sc_time& time) {} + void set_done() {} + void set_fail(); + void process(); + bool active() const; + bool busy() const; + bool failed() const; + bool advance_time() const; + const sc_core::sc_time& get_time_step(); + +protected: + scd_cont_man_master& _fsm; + std::list& _slaves; + sc_core::sc_time& _time_step; + scd_cont_state& _st_init; + scd_cont_state& _st_busy; + scd_cont_state& _st_idle; + scd_cont_state& _st_done; + scd_cont_state& _st_time_req; + scd_cont_state& _st_time; + scd_cont_state& _st_term_req; + scd_cont_state& _st_terminate; + scd_cont_state& _st_terminated; + scd_cont_state& _st_fail; + scd_cont_state& _st_failed; + + /* member functions */ + bool _check_slaves(); + bool _some_slaves_active(); + void _close_slaves(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.cpp new file mode 100644 index 0000000..fcb322c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.cpp @@ -0,0 +1,34 @@ +#include "fsm/scd_stm_busy.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_busy::set_idle(const sc_core::sc_time& time) +{ + /* prevents the situation where no events exist, but a huge + * ammount of data is being transferred that will generate events later + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + _time_step = time; + _fsm.set_state(_st_idle); +} + + +void scd_stm_busy::set_done() +{ + /* prevents the situation where no events exist, but a huge + * ammount of data is being transferred that will generate events later + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + _time_step = sc_core::SC_ZERO_TIME; + _fsm.set_state(_st_done); +} + + +bool scd_stm_busy::busy() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.h new file mode 100644 index 0000000..ae9eb1d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_busy.h @@ -0,0 +1,22 @@ +#ifndef SCD_STM_BUSY_H +#define SCD_STM_BUSY_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_busy : public scd_stm_base +{ +public: + scd_stm_busy(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "busy"; } + virtual ~scd_stm_busy() {} + + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_done(); + bool busy() const; + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.cpp new file mode 100644 index 0000000..9d54d60 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.cpp @@ -0,0 +1,64 @@ +#include "fsm/scd_stm_done.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_done::set_busy() +{ + _fsm.set_state(_st_busy); +} + + +void scd_stm_done::set_idle(const sc_core::sc_time& time) +{ + _time_step = time; + _fsm.set_state(_st_idle); +} + + +void scd_stm_done::process() +{ + if (!_check_slaves()) + return; + + std::list::iterator iter; + bool terminate = true; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if (!(*iter)->idle() && !(*iter)->done()) + { + // this slave is not ready yet + return; + } + + if (!(*iter)->done()) + terminate = false; + } + + /* all slaves seem idle or done. wait a short moment, to prevent + * race conditions (channel data in transit but both endpoints done). + * if no socket activity occured we can move on, else we cancel. + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + if (terminate) + { + // all slaves seem to be done + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_term_req(); + _fsm.save_state(); + _fsm.set_state(_st_term_req); + } + else + { + // some slaves are not done yet + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_time_req(); + _fsm.save_state(); + _fsm.set_state(_st_time_req); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.h new file mode 100644 index 0000000..de20874 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_done.h @@ -0,0 +1,21 @@ +#ifndef SCD_STM_DONE_H +#define SCD_STM_DONE_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_done : public scd_stm_base +{ +public: + scd_stm_done(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "done"; } + virtual ~scd_stm_done() {} + + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.cpp new file mode 100644 index 0000000..53b13d3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.cpp @@ -0,0 +1,17 @@ +#include "fsm/scd_stm_fail.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_fail::process() +{ + if (!_some_slaves_active()) + { + // all slaves have terminated + _sim.get_chan_man().close(); + _close_slaves(); + _fsm.set_state(_st_failed); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.h new file mode 100644 index 0000000..38b9e87 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_fail.h @@ -0,0 +1,25 @@ +#ifndef SCD_STM_FAIL_H +#define SCD_STM_FAIL_H + +#include "fsm/scd_stm_base.h" + + +/* forward declaration */ +class scd_cont_man_master; + + +class scd_stm_fail : public scd_stm_base +{ +public: + scd_stm_fail(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "fail"; } + + virtual ~scd_stm_fail() {} + + + /* scd_cont_fsm_if */ + void set_fail() {} + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.cpp new file mode 100644 index 0000000..fd0cd9b --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.cpp @@ -0,0 +1,10 @@ +#include "fsm/scd_stm_failed.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +bool scd_stm_failed::active() const { return false; } + + +bool scd_stm_failed::failed() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.h new file mode 100644 index 0000000..148a56b --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_failed.h @@ -0,0 +1,25 @@ +#ifndef SCD_STM_FAILED_H +#define SCD_STM_FAILED_H + +#include "fsm/scd_stm_base.h" + + +/* forward declaration */ +class scd_cont_man_master; + + +class scd_stm_failed : public scd_stm_base +{ +public: + scd_stm_failed(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "failed"; } + virtual ~scd_stm_failed() {} + + /* scd_cont_fsm_if */ + void set_fail() {} + void process() {} + bool active() const; + bool failed() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.cpp new file mode 100644 index 0000000..1138ab7 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.cpp @@ -0,0 +1,50 @@ +#include "fsm/scd_stm_idle.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_idle::set_busy() +{ + _fsm.set_state(_st_busy); +} + + +void scd_stm_idle::set_done() +{ + _time_step = sc_core::SC_ZERO_TIME; + _fsm.set_state(_st_done); +} + + +void scd_stm_idle::process() +{ + if (!_check_slaves()) + return; + + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if (!(*iter)->idle() && !(*iter)->done()) + { + // this slave is not ready yet + return; + } + } + + /* all slaves seem idle or done. wait a short moment, to prevent + * race conditions (channel data in transit but both endpoints done). + * if no socket activity occured we can move on, else we cancel. + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + // all slaves seem to be ready to advance time + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_time_req(); + + _fsm.save_state(); + _fsm.set_state(_st_time_req); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.h new file mode 100644 index 0000000..b19e4f0 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_idle.h @@ -0,0 +1,20 @@ +#ifndef SCD_STM_IDLE_H +#define SCD_STM_IDLE_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_idle : public scd_stm_base +{ +public: + scd_stm_idle(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "idle"; } + virtual ~scd_stm_idle() {} + + /* scd_cont_fsm_if */ + void set_busy(); + void set_done(); + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.cpp new file mode 100644 index 0000000..3027ad8 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.cpp @@ -0,0 +1,58 @@ +#include "fsm/scd_stm_init.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_init::set_idle(const sc_core::sc_time& time) +{ + scd_error("init: illegal call to set_idle()"); + throw scd_exception("illegal call"); +} + + +void scd_stm_init::set_busy() +{ + scd_error("init: illegal call to set_busy()"); + throw scd_exception("illegal call"); +} + + +void scd_stm_init::set_done() +{ + scd_error("init: illegal call to set_done()"); + throw scd_exception("illegal call"); +} + + +void scd_stm_init::process() +{ + // check for failed slaves and react + if (!_check_slaves()) + return; + + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + // if this is still in init state we terminate + if ( !(*iter)->active() ) + return; + } + + // all slaves are active + scd_info("all slaves connected"); + _fsm.set_state(_st_busy); + +} // process() + + +bool scd_stm_init::active() const { return false; } + + +bool scd_stm_init::advance_time() const +{ + scd_error("illegal call to advance_time()"); + throw scd_exception("illegal call"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.h new file mode 100644 index 0000000..b146a46 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_init.h @@ -0,0 +1,27 @@ +#ifndef SCD_STM_INIT_H +#define SCD_STM_INIT_H + +#include "fsm/scd_stm_base.h" + + +/* forward declaration */ +class scd_cont_man_master; + + +class scd_stm_init : public scd_stm_base +{ +public: + scd_stm_init(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "init"; } + virtual ~scd_stm_init() {} + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_busy(); + void set_done(); + void process(); + bool active() const; + bool advance_time() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.cpp new file mode 100644 index 0000000..1d65952 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.cpp @@ -0,0 +1,59 @@ +#include "fsm/scd_stm_term_req.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_term_req::set_busy() +{ + _send_term_nack(); + _fsm.load_state(); +} + + +void scd_stm_term_req::set_idle(const sc_core::sc_time& time) +{ + _send_term_nack(); + _fsm.load_state(); +} + + +void scd_stm_term_req::process() +{ + if (!_check_slaves()) + return; + + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if ( !(*iter)->term_req() && !(*iter)->term_ack() ) + { + // received term_nack from this slave + _send_term_nack(); + _fsm.load_state(); + return; + } + else if ( !(*iter)->term_ack() ) + { + // this slave did not respond yet + return; + } + } + + // all slaves acknkowledged + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_term(); + + _fsm.set_state(_st_terminate); +} + + +void scd_stm_term_req::_send_term_nack() +{ + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_term_nack(); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.h new file mode 100644 index 0000000..b93e082 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_term_req.h @@ -0,0 +1,23 @@ +#ifndef SCD_STM_TERM_REQ_H +#define SCD_STM_TERM_REQ_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_term_req : public scd_stm_base +{ +public: + scd_stm_term_req(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "term_req"; } + virtual ~scd_stm_term_req() {} + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void process(); + +private: + void _send_term_nack(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.cpp new file mode 100644 index 0000000..3035321 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.cpp @@ -0,0 +1,40 @@ +#include "fsm/scd_stm_terminate.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_terminate::set_busy() +{ + scd_error("received further events while terminating"); + set_fail(); +} + + +void scd_stm_terminate::set_idle(const sc_core::sc_time& time) +{ + scd_error("received future events while terminating"); + set_fail(); +} + + +void scd_stm_terminate::process() +{ + // check for failed slaves and react + if (!_check_slaves()) + return; + + if (_some_slaves_active()) + { + // not all slaves have terminated yet + return; + } + else + { + // all slaves terminated + _sim.get_chan_man().close(); + _close_slaves(); + _fsm.set_state(_st_terminated); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.h new file mode 100644 index 0000000..f568d8c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminate.h @@ -0,0 +1,21 @@ +#ifndef SCD_STM_TERMINATE_H +#define SCD_STM_TERMINATE_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_terminate : public scd_stm_base +{ +public: + scd_stm_terminate(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "terminate"; } + virtual ~scd_stm_terminate() {} + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void process(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.cpp new file mode 100644 index 0000000..5d7e165 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.cpp @@ -0,0 +1,8 @@ +#include "fsm/scd_stm_terminated.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +bool scd_stm_terminated::active() const { return false; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.h new file mode 100644 index 0000000..2551b78 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_terminated.h @@ -0,0 +1,21 @@ +#ifndef SCD_STM_TERMINATED_H +#define SCD_STM_TERMINATED_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_terminated : public scd_stm_base +{ +public: + scd_stm_terminated(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "terminated"; } + virtual ~scd_stm_terminated() {} + + /* scd_cont_fsm_if */ + void set_fail() {} + void process() {} + bool active() const; + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.cpp new file mode 100644 index 0000000..89df203 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.cpp @@ -0,0 +1,15 @@ +#include "fsm/scd_stm_time.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +bool scd_stm_time::advance_time() const { return true; } + + +const sc_core::sc_time& scd_stm_time::get_time_step() +{ + _fsm.set_state(_st_busy); + return _time_step; +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.h new file mode 100644 index 0000000..52e0a59 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time.h @@ -0,0 +1,19 @@ +#ifndef SCD_STM_TIME_H +#define SCD_STM_TIME_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_time : public scd_stm_base +{ +public: + scd_stm_time(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "time"; } + virtual ~scd_stm_time() {} + + /* scd_cont_fsm_if */ + bool advance_time() const; + const sc_core::sc_time& get_time_step(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.cpp new file mode 100644 index 0000000..fb96b26 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.cpp @@ -0,0 +1,64 @@ +#include "fsm/scd_stm_time_req.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_master.h" + + +void scd_stm_time_req::set_busy() +{ + _send_time_nack(); + _fsm.load_state(); +} + + +void scd_stm_time_req::process() +{ + if (!_check_slaves()) + return; + + std::list::iterator iter; + sc_core::sc_time min_step = _time_step; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + { + if ( !(*iter)->time_req() && !(*iter)->time_ack() ) + { + // received time_nack from this slave + _send_time_nack(); + _fsm.load_state(); + return; + } + else if ( !(*iter)->time_ack() ) + { + // this slave did not respond yet + return; + } + else + { + // received time_ack => search the smallest step + sc_core::sc_time time_slave = (*iter)->get_time_step(); + if (min_step == sc_core::SC_ZERO_TIME) + min_step = time_slave; + else if (min_step > time_slave && + time_slave != sc_core::SC_ZERO_TIME) + min_step = time_slave; + } + } + + // all slaves acknkowledged + _time_step = min_step; + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_time(min_step); + + _fsm.set_state(_st_time); +} + + +void scd_stm_time_req::_send_time_nack() +{ + std::list::iterator iter; + + for (iter = _slaves.begin(); iter != _slaves.end(); iter++) + (*iter)->send_time_nack(); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.h new file mode 100644 index 0000000..275bd95 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stm_time_req.h @@ -0,0 +1,22 @@ +#ifndef SCD_STM_TIME_REQ_H +#define SCD_STM_TIME_REQ_H + +#include "fsm/scd_stm_base.h" + + +class scd_stm_time_req : public scd_stm_base +{ +public: + scd_stm_time_req(scd_simulator& sim, scd_cont_man_master& fsm): + scd_stm_base(sim, fsm) { _name = "time_req"; } + virtual ~scd_stm_time_req() {} + + /* scd_cont_fsm_if */ + void set_busy(); + void process(); + +private: + void _send_time_nack(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.cpp new file mode 100644 index 0000000..b4d0af0 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.cpp @@ -0,0 +1,81 @@ +#include "fsm/scd_sts_base.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" +#include "scd_command.h" +#include "scd_cont_man.h" + + + +scd_sts_base::scd_sts_base(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_cont_state(sim), _fsm(fsm), + _st_init(fsm._st_init), _st_busy(fsm._st_busy), _st_idle(fsm._st_idle), + _st_done(fsm._st_done), _st_time_ack(fsm._st_time_ack), + _st_time(fsm._st_time), _st_term_ack(fsm._st_term_ack), + _st_terminated(fsm._st_terminated), _st_fail(fsm._st_fail), + _st_failed(fsm._st_failed), + _writer(fsm._writer), _reader(fsm._reader), _connector(fsm._connector) +{ +} + + +void scd_sts_base::set_failed() +{ + _sim.get_poller().remove_handler(_fsm); + _sim.get_chan_man().close(); + _fsm.set_state(_st_failed); +} + + +void scd_sts_base::recv_time_req() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_NACK); + _fsm.send_command(cmd); +} + + +void scd_sts_base::recv_time(const sc_core::sc_time& time) +{ + scd_warn("received time message in wrong state"); +} + + +void scd_sts_base::recv_term_req() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_NACK); + _fsm.send_command(cmd); +} + + +void scd_sts_base::recv_term() +{ + scd_warn("received terminate message in wrong state"); +} + + +void scd_sts_base::set_fail() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_FAILED); + _fsm.send_command(cmd); + _fsm.set_state(_st_fail); +} + + +bool scd_sts_base::active() const { return true; } + + +bool scd_sts_base::busy() const { return false; } + + +bool scd_sts_base::failed() const { return false; } + + +bool scd_sts_base::advance_time() const { return false; } + + +const sc_core::sc_time& scd_sts_base::get_time_step() +{ + scd_warn("illegal call to get_time_step()"); + throw scd_exception("illegal call"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.h new file mode 100644 index 0000000..05c1e8d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_base.h @@ -0,0 +1,101 @@ +#ifndef SCD_STS_BASE_H +#define SCD_STS_BASE_H + +#include "scd_simulator.h" +#include "scd_out_connector.h" +#include "scd_command_writer.h" +#include "scd_command_reader.h" +#include "fsm/scd_cont_state.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +/** + * Base class for all control slave states. + */ +class scd_sts_base : public scd_cont_state +{ +public: + /** + * Constructor. + * \param sim the simulation environment + * \param fsm the FSM of this state + */ + scd_sts_base(scd_simulator& sim, scd_cont_man_slave& fsm); + + virtual ~scd_sts_base() {} + + /** + * Signalizes the state, that a failed message has been received + * from the master. + */ + virtual void set_failed(); + + /** + * Signalizes the state, that a time_req message has been received + * from the master. + */ + virtual void recv_time_req(); + + /** + * Signalizes the state, that a time_nack message has been received + * from the master. + */ + virtual void recv_time_nack() {} + + /** + * Signalizes the state, that a time message has been received + * from the master. + */ + virtual void recv_time(const sc_core::sc_time& time); + + /** + * Signalizes the state, that a term_req message has been received + * from the master. + */ + virtual void recv_term_req(); + + /** + * Signalizes the state, that a term_nack message has been received + * from the master. + */ + virtual void recv_term_nack() {} + + /** + * Signalizes the state, that a term message has been received + * from the master. + */ + virtual void recv_term(); + + /* scd_cont_fsm_if */ + void set_busy() {} + void set_idle(const sc_core::sc_time& time) {} + void set_done() {} + void set_fail(); + void process() {} + bool active() const; + bool busy() const; + bool failed() const; + bool advance_time() const; + const sc_core::sc_time& get_time_step(); + +protected: + scd_cont_man_slave& _fsm; + scd_command_writer& _writer; + scd_command_reader& _reader; + scd_out_connector& _connector; + scd_cont_state& _st_init; + scd_cont_state& _st_busy; + scd_cont_state& _st_idle; + scd_cont_state& _st_done; + scd_cont_state& _st_time_ack; + scd_cont_state& _st_time; + scd_cont_state& _st_term_ack; + scd_cont_state& _st_terminated; + scd_cont_state& _st_fail; + scd_cont_state& _st_failed; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.cpp new file mode 100644 index 0000000..a9bb214 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.cpp @@ -0,0 +1,36 @@ +#include "fsm/scd_sts_busy.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_busy::set_idle(const sc_core::sc_time& time) +{ + /* prevents the situation where no events exist, but a huge + * ammount of data is being transferred that will generate events later + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_IDLE, time); + _fsm.send_command(cmd); + _fsm.set_state(_st_idle); +} + + +void scd_sts_busy::set_done() +{ + /* prevents the situation where no events exist, but a huge + * ammount of data is being transferred that will generate events later + */ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + return; + + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_DONE); + _fsm.send_command(cmd); + _fsm.set_state(_st_done); +} + + +bool scd_sts_busy::busy() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.h new file mode 100644 index 0000000..b2130ad --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_busy.h @@ -0,0 +1,21 @@ +#ifndef SCD_STS_BUSY_H +#define SCD_STS_BUSY_H + +#include "fsm/scd_sts_base.h" + + +class scd_sts_busy : public scd_sts_base +{ +public: + scd_sts_busy(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "busy"; } + virtual ~scd_sts_busy() {} + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_done(); + bool busy() const; + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.cpp new file mode 100644 index 0000000..ddd8203 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.cpp @@ -0,0 +1,58 @@ +#include "fsm/scd_sts_done.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" +#include "scd_cont_man.h" + + +void scd_sts_done::recv_time_req() +{ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + { + // had activity on sockets => we might be receiving data + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_NACK); + _fsm.send_command(cmd); + } + else + { + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_ACK); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_time_ack); + } +} + + +void scd_sts_done::recv_term_req() +{ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + { + // had activity on sockets => we might be receiving data + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_NACK); + _fsm.send_command(cmd); + } + else + { + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_ACK); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_term_ack); + } +} + + +void scd_sts_done::set_idle(const sc_core::sc_time& time) +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_IDLE, time); + _fsm.send_command(cmd); + _fsm.set_state(_st_idle); +} + + +void scd_sts_done::set_busy() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_BUSY); + _fsm.send_command(cmd); + _fsm.set_state(_st_busy); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.h new file mode 100644 index 0000000..14e762f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_done.h @@ -0,0 +1,27 @@ +#ifndef SCD_STS_DONE_H +#define SCD_STS_DONE_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_done : public scd_sts_base +{ +public: + scd_sts_done(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "done"; } + virtual ~scd_sts_done() {} + + void recv_time_req(); + void recv_term_req(); + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_busy(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.cpp new file mode 100644 index 0000000..b026fa2 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.cpp @@ -0,0 +1,17 @@ +#include "fsm/scd_sts_fail.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_fail::process() +{ + if (!_fsm.is_sending()) + { + _sim.get_poller().remove_handler(_fsm); + _sim.get_chan_man().close(); + _fsm.close(); + _fsm.set_state(_st_failed); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.h new file mode 100644 index 0000000..dc2fb1d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_fail.h @@ -0,0 +1,28 @@ +#ifndef SCD_STS_FAIL_H +#define SCD_STS_FAIL_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_fail : public scd_sts_base +{ +public: + scd_sts_fail(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "fail"; } + virtual ~scd_sts_fail() {} + + void recv_time_req() {} + void recv_time(const sc_core::sc_time& time) {} + void recv_term_req() {} + void recv_term() {} + + /* scd_cont_fsm_if */ + void set_fail() {} + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.cpp new file mode 100644 index 0000000..5e6966d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.cpp @@ -0,0 +1,11 @@ +#include "fsm/scd_sts_failed.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + + +bool scd_sts_failed::active() const { return false; } + + +bool scd_sts_failed::failed() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.h new file mode 100644 index 0000000..c699dbb --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_failed.h @@ -0,0 +1,31 @@ +#ifndef SCD_STS_FAILED_H +#define SCD_STS_FAILED_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_failed : public scd_sts_base +{ +public: + scd_sts_failed(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "failed"; } + virtual ~scd_sts_failed() {} + + void set_failed() {} + + void recv_time_req() {} + void recv_time(const sc_core::sc_time& time) {} + void recv_term_req() {} + void recv_term() {} + + /* scd_cont_fsm_if */ + void set_fail() {} + bool active() const; + bool failed() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.cpp new file mode 100644 index 0000000..cbb0f94 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.cpp @@ -0,0 +1,37 @@ +#include "fsm/scd_sts_idle.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_idle::recv_time_req() +{ + if (_sim.get_poller().wait(SCD_CONT_DELAY)) + { + // had activity on sockets => we might be receiving data + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_NACK); + _fsm.send_command(cmd); + } + else + { + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_ACK); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_time_ack); + } +} + + +void scd_sts_idle::set_busy() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_BUSY); + _fsm.send_command(cmd); + _fsm.set_state(_st_busy); +} + + +void scd_sts_idle::set_done() +{ + scd_warn("slave: transition attempt from idle to done"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.h new file mode 100644 index 0000000..df68cd1 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_idle.h @@ -0,0 +1,25 @@ +#ifndef SCD_STS_IDLE_H +#define SCD_STS_IDLE_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_idle : public scd_sts_base +{ +public: + scd_sts_idle(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "idle"; } + virtual ~scd_sts_idle() {} + + void recv_time_req(); + + /* scd_cont_fsm_if */ + void set_busy(); + void set_done(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.cpp new file mode 100644 index 0000000..d4d05e9 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.cpp @@ -0,0 +1,70 @@ +#include "fsm/scd_sts_init.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_init::set_failed() +{ + _fsm.set_state(_st_failed); +} + + +void scd_sts_init::set_idle(const sc_core::sc_time& time) +{ + scd_error("init: illegal call to set_idle()"); + throw scd_exception("illegal call"); +} + + +void scd_sts_init::set_busy() +{ + scd_error("init: illegal call to set_busy()"); + throw scd_exception("illegal call"); +} + + +void scd_sts_init::set_done() +{ + scd_error("init: illegal call to set_done()"); + throw scd_exception("illegal call"); +} + + +void scd_sts_init::set_fail() +{ + _fsm.set_state(_st_failed); +} + + +void scd_sts_init::process() +{ + _connector.process(); + + if (!_connector.is_connecting() && !_connector.has_connection()) + { + // unable to connect to master + scd_error("connecting to master failed"); + _fsm.set_state(_st_failed); + } + else if (!_connector.is_connecting() && _connector.has_connection()) + { + // successfully connected to master + scd_info("connected to master"); + _fsm.set_socket(); + _sim.get_poller().register_handler(_fsm, SOCK_EV_READ | SOCK_EV_CLOSE); + _fsm.set_state(_st_busy); + } + +} // process() + + +bool scd_sts_init::active() const { return false; } + + +bool scd_sts_init::advance_time() const +{ + scd_error("illegal call to advance_time()"); + throw scd_exception("illegal call"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.h new file mode 100644 index 0000000..3fff4ea --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_init.h @@ -0,0 +1,30 @@ +#ifndef SCD_STS_INIT_H +#define SCD_STS_INIT_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_init : public scd_sts_base +{ +public: + scd_sts_init(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "init"; } + virtual ~scd_sts_init() {} + + void set_failed(); + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_busy(); + void set_done(); + void set_fail(); + void process(); + bool active() const; + bool advance_time() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.cpp new file mode 100644 index 0000000..e3433ff --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.cpp @@ -0,0 +1,36 @@ +#include "fsm/scd_sts_term_ack.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_term_ack::recv_time_req() +{ + scd_warn("received time request in illegal state"); +} + + +void scd_sts_term_ack::recv_term_req() +{ + scd_warn("received terminate request in illegal state"); +} + + +void scd_sts_term_ack::recv_term_nack() +{ + _fsm.load_state(); +} + + +void scd_sts_term_ack::recv_term() +{ + _sim.get_poller().remove_handler(_fsm); + _fsm.set_state(_st_terminated); +} + + +void scd_sts_term_ack::set_busy() +{ + scd_warn("received channel data while synchronizing to terminate"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.h new file mode 100644 index 0000000..2a32250 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_term_ack.h @@ -0,0 +1,28 @@ +#ifndef SCD_STS_TERM_ACK_H +#define SCD_STS_TERM_ACK_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_term_ack : public scd_sts_base +{ +public: + scd_sts_term_ack(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "term_ack"; } + virtual ~scd_sts_term_ack() {} + + void recv_time_req(); + void recv_term_req(); + void recv_term_nack(); + void recv_term(); + + void set_fail() {} + void set_busy(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.cpp new file mode 100644 index 0000000..7bd4233 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.cpp @@ -0,0 +1,19 @@ +#include "fsm/scd_sts_terminated.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +void scd_sts_terminated::set_busy() +{ + scd_error("received further events while synchronizing (terminate)"); +} + + +void scd_sts_terminated::set_idle(const sc_core::sc_time& time) +{ + scd_error("received future events while synchronizing (terminate)"); +} + + +bool scd_sts_terminated::active() const { return false; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.h new file mode 100644 index 0000000..d05996a --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_terminated.h @@ -0,0 +1,30 @@ +#ifndef SCD_STS_TERMINATED_H +#define SCD_STS_TERMINATED_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_terminated : public scd_sts_base +{ +public: + scd_sts_terminated(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "terminated"; } + virtual ~scd_sts_terminated() {} + + void set_failed() {} + void recv_time_req() {} + void recv_term_req() {} + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void set_fail() {} + bool active() const; + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.cpp new file mode 100644 index 0000000..09e69e5 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.cpp @@ -0,0 +1,21 @@ +#include "fsm/scd_sts_time.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_time::set_time_step(const sc_core::sc_time& time) +{ + _time_step = time; +} + + +bool scd_sts_time::advance_time() const { return true; } + + +const sc_core::sc_time& scd_sts_time::get_time_step() +{ + _fsm.set_state(_st_busy); + return _time_step; +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.h new file mode 100644 index 0000000..8467342 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time.h @@ -0,0 +1,28 @@ +#ifndef SCD_STS_TIME_H +#define SCD_STS_TIME_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_time : public scd_sts_base +{ +public: + scd_sts_time(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "time"; } + virtual ~scd_sts_time() {} + + void set_time_step(const sc_core::sc_time& time); + + /* scd_cont_fsm_if */ + bool advance_time() const; + const sc_core::sc_time& get_time_step(); + +private: + sc_core::sc_time _time_step; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.cpp new file mode 100644 index 0000000..53c6827 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.cpp @@ -0,0 +1,36 @@ +#include "fsm/scd_sts_time_ack.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_man_slave.h" + + +void scd_sts_time_ack::recv_time_req() +{ + scd_warn("received time request in wrong state"); +} + + +void scd_sts_time_ack::recv_time_nack() +{ + _fsm.load_state(); +} + + +void scd_sts_time_ack::recv_time(const sc_core::sc_time& time) +{ + static_cast(_st_time).set_time_step(time); + _fsm.set_state(_st_time); +} + + +void scd_sts_time_ack::recv_term_req() +{ + scd_warn("received terminate request in wrong state"); +} + + +void scd_sts_time_ack::set_busy() +{ + scd_warn("received channel data while synchronizing to terminate"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.h new file mode 100644 index 0000000..ef2c39a --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_sts_time_ack.h @@ -0,0 +1,28 @@ +#ifndef SCD_STS_TIME_ACK_H +#define SCD_STS_TIME_ACK_H + +#include "fsm/scd_sts_base.h" + + +/* forward declaration */ +class scd_cont_man_slave; + + +class scd_sts_time_ack : public scd_sts_base +{ +public: + scd_sts_time_ack(scd_simulator& sim, scd_cont_man_slave& fsm): + scd_sts_base(sim, fsm) { _name = "time_ack"; } + virtual ~scd_sts_time_ack() {} + + /* scd_sts_base */ + void recv_time_req(); + void recv_time_nack(); + void recv_time(const sc_core::sc_time& time); + void recv_term_req(); + + void set_busy(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.cpp new file mode 100644 index 0000000..a4f0c46 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.cpp @@ -0,0 +1,125 @@ +#include "fsm/scd_stsw_base.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" +#include "scd_command.h" + + +scd_stsw_base::scd_stsw_base(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_cont_state(sim), _fsm(fsm), _time_step(fsm._time_step), + _st_init(fsm._st_init), _st_busy(fsm._st_busy), _st_idle(fsm._st_idle), + _st_done(fsm._st_done), _st_time_req(fsm._st_time_req), + _st_time_ack(fsm._st_time_ack), _st_term_req(fsm._st_term_req), + _st_term_ack(fsm._st_term_ack), _st_terminate(fsm._st_terminate), + _st_terminated(fsm._st_terminated), _st_fail(fsm._st_fail), + _st_failed(fsm._st_failed) +{ +} + + +void scd_stsw_base::set_connected() +{ + scd_warn("illegal call to set_connected()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::set_failed() +{ + _sim.get_poller().remove_handler(_fsm); + _fsm.set_state(_st_failed); +} + + +void scd_stsw_base::send_time_req() +{ + scd_warn("illegal call to send_time_req()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::send_time(const sc_core::sc_time& time) +{ + scd_warn("illegal call to send_time()"); + throw scd_exception("illegal call"); +} + +void scd_stsw_base::send_term_req() +{ + scd_warn("illegal call to send_term_req()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::send_term() +{ + scd_warn("illegal call to send_term()"); + throw scd_exception("illegal call"); +} + + +bool scd_stsw_base::time_req() const { return false; } + + +bool scd_stsw_base::time_ack() const { return false; } + + +bool scd_stsw_base::term_req() const { return false; } + + +bool scd_stsw_base::term_ack() const { return false; } + + +bool scd_stsw_base::idle() const { return false; } + + +bool scd_stsw_base::done() const { return false; } + + +void scd_stsw_base::set_busy() +{ + scd_error("illegal call to set_busy()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::set_idle(const sc_core::sc_time& time) +{ + scd_error("illegal call to set_idle()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::set_done() +{ + scd_error("illegal call to set_done()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_base::set_fail() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_FAILED); + _fsm.send_command(cmd); + _fsm.set_state(_st_fail); +} + + +bool scd_stsw_base::active() const { return true; } + + +bool scd_stsw_base::busy() const { return false; } + + +bool scd_stsw_base::failed() const { return false; } + + +bool scd_stsw_base::advance_time() const { return false; } + + +const sc_core::sc_time& scd_stsw_base::get_time_step() +{ + scd_error("illegal call to get_time_step()"); + throw scd_exception("illegal call"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.h new file mode 100644 index 0000000..8ac5750 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_base.h @@ -0,0 +1,106 @@ +#ifndef SCD_STSW_BASE_H +#define SCD_STSW_BASE_H + +#include "scd_simulator.h" +#include "fsm/scd_cont_state.h" +#include "fsm/scd_cont_wrapper_if.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +/** + * Base class for all control slave wrapper states. + */ +class scd_stsw_base : public scd_cont_state, public scd_cont_wrapper_if +{ +public: + /** + * Constructor. + * \param sim the simulation environment + * \param fsm the FSM of this state + */ + scd_stsw_base(scd_simulator& sim, scd_cont_slave_wrapper& fsm); + + virtual ~scd_stsw_base() {} + + /** + * Signalizes the state that the slave has been connected to the wrapper. + */ + virtual void set_connected(); + + /** + * Signalizes the state that a failed command has been received + * from the slave. + */ + virtual void set_failed(); + + /** + * Signalizes the state that a time_nack command has been received + * from the slave. + */ + virtual void recv_time_nack() {} + + /** + * Signalizes the state that a time_ack command has been received + * from the slave. + */ + virtual void recv_time_ack() {} + + /** + * Signalizes the state that a term_nack command has been received + * from the slave. + */ + virtual void recv_term_nack() {} + + /** + * Signalizes the state that a term_ack command has been received + * from the slave. + */ + virtual void recv_term_ack() {} + + /* scd_cont_wrapper_if */ + void send_time_req(); + void send_time_nack() {} + void send_time(const sc_core::sc_time& time); + void send_term_req(); + void send_term_nack() {}; + void send_term(); + bool time_req() const; + bool time_ack() const; + bool term_req() const; + bool term_ack() const; + bool idle() const; + bool done() const; + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void set_done(); + void set_fail(); + void process() {}; + bool active() const; + bool busy() const; + bool failed() const; + bool advance_time() const; + const sc_core::sc_time& get_time_step(); + +protected: + scd_cont_slave_wrapper& _fsm; + sc_core::sc_time& _time_step; + scd_cont_state& _st_init; + scd_cont_state& _st_busy; + scd_cont_state& _st_idle; + scd_cont_state& _st_done; + scd_cont_state& _st_time_req; + scd_cont_state& _st_time_ack; + scd_cont_state& _st_term_req; + scd_cont_state& _st_term_ack; + scd_cont_state& _st_terminate; + scd_cont_state& _st_terminated; + scd_cont_state& _st_fail; + scd_cont_state& _st_failed; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.cpp new file mode 100644 index 0000000..f96ba77 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.cpp @@ -0,0 +1,22 @@ +#include "fsm/scd_stsw_busy.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_busy::set_idle(const sc_core::sc_time& time) +{ + _time_step = time; + _fsm.set_state(_st_idle); +} + + +void scd_stsw_busy::set_done() +{ + _time_step = sc_core::SC_ZERO_TIME; + _fsm.set_state(_st_done); +} + + +bool scd_stsw_busy::busy() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.h new file mode 100644 index 0000000..1194bdf --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_busy.h @@ -0,0 +1,21 @@ +#ifndef SCD_STSW_BUSY_H +#define SCD_STSW_BUSY_H + +#include "fsm/scd_stsw_base.h" + + +class scd_stsw_busy : public scd_stsw_base +{ +public: + scd_stsw_busy(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "busy"; } + virtual ~scd_stsw_busy() {} + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_done(); + bool busy() const; + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.cpp new file mode 100644 index 0000000..329b81b --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.cpp @@ -0,0 +1,39 @@ +#include "fsm/scd_stsw_done.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_done::send_time_req() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_REQ); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_time_req); +} + + +void scd_stsw_done::send_term_req() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_REQ); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_term_req); +} + + +bool scd_stsw_done::done() const { return true; } + + +void scd_stsw_done::set_busy() +{ + _fsm.set_state(_st_busy); +} + + +void scd_stsw_done::set_idle(const sc_core::sc_time& time) +{ + _time_step = time; + _fsm.set_state(_st_idle); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.h new file mode 100644 index 0000000..148d973 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_done.h @@ -0,0 +1,28 @@ +#ifndef SCD_STSW_DONE_H +#define SCD_STSW_DONE_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_done : public scd_stsw_base +{ +public: + scd_stsw_done(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "done"; } + virtual ~scd_stsw_done() {} + + /* scd_cont_wrapper_if */ + void send_time_req(); + void send_term_req(); + bool done() const; + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.cpp new file mode 100644 index 0000000..51348d3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.cpp @@ -0,0 +1,18 @@ +#include "fsm/scd_stsw_fail.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_fail::set_fail() {} + + +void scd_stsw_fail::process() +{ + if (!_fsm.is_sending()) + { + _sim.get_poller().remove_handler(_fsm); + _fsm.set_state(_st_failed); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.h new file mode 100644 index 0000000..cb8fe22 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_fail.h @@ -0,0 +1,23 @@ +#ifndef SCD_STSW_FAIL_H +#define SCD_STSW_FAIL_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_fail : public scd_stsw_base +{ +public: + scd_stsw_fail(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "fail"; } + virtual ~scd_stsw_fail() {} + + /* scd_cont_fsm_if */ + void set_fail(); + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.cpp new file mode 100644 index 0000000..ab2db2e --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.cpp @@ -0,0 +1,16 @@ +#include "fsm/scd_stsw_failed.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +void scd_stsw_failed::set_failed() {} + + +void scd_stsw_failed::set_fail() {} + + +bool scd_stsw_failed::active() const { return false; } + + +bool scd_stsw_failed::failed() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.h new file mode 100644 index 0000000..cf13d9d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_failed.h @@ -0,0 +1,26 @@ +#ifndef SCD_STSW_FAILED_H +#define SCD_STSW_FAILED_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_failed : public scd_stsw_base +{ +public: + scd_stsw_failed(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "failed"; } + virtual ~scd_stsw_failed() {} + + void set_failed(); + + /* scd_cont_fsm_if */ + void set_fail(); + bool active() const; + bool failed() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.cpp new file mode 100644 index 0000000..6883648 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.cpp @@ -0,0 +1,29 @@ +#include "fsm/scd_stsw_idle.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_idle::send_time_req() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_REQ); + _fsm.send_command(cmd); + _fsm.save_state(); + _fsm.set_state(_st_time_req); +} + + +bool scd_stsw_idle::idle() const { return true; } + + +void scd_stsw_idle::set_busy() +{ + _fsm.set_state(_st_busy); +} + + +void scd_stsw_idle::set_done() +{ + scd_warn("slave " + _fsm.get_name() + " transition attempt from idle to done"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.h new file mode 100644 index 0000000..4af1c6d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_idle.h @@ -0,0 +1,28 @@ +#ifndef SCD_STSW_IDLE_H +#define SCD_STSW_IDLE_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_idle : public scd_stsw_base +{ +public: + scd_stsw_idle(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "idle"; } + virtual ~scd_stsw_idle() {} + + /* scd_cont_wrapper_if */ + void send_time_req(); + bool idle() const; + + /* scd_cont_fsm_if */ + void set_busy(); + void set_done(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.cpp new file mode 100644 index 0000000..d4f27bd --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.cpp @@ -0,0 +1,58 @@ +#include "fsm/scd_stsw_init.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_init::set_connected() +{ + _sim.get_poller().register_handler(_fsm, SOCK_EV_READ | SOCK_EV_CLOSE); + _fsm.set_state(_st_busy); +} + + +void scd_stsw_init::set_failed() +{ + _fsm.set_state(_st_failed); +} + + +void scd_stsw_init::set_idle(const sc_core::sc_time& time) +{ + scd_error("init: illegal call to set_idle()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_init::set_busy() +{ + scd_error("init: illegal call to set_idle()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_init::set_done() +{ + scd_error("init: illegal call to set_done()"); + throw scd_exception("illegal call"); +} + + +void scd_stsw_init::set_fail() +{ + _fsm.set_state(_st_failed); +} + + +void scd_stsw_init::process() {} + + +bool scd_stsw_init::active() const { return false; } + + +bool scd_stsw_init::advance_time() const +{ + scd_error("illegal call to advance_time()"); + throw scd_exception("illegal call"); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.h new file mode 100644 index 0000000..aa4d8a3 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_init.h @@ -0,0 +1,31 @@ +#ifndef SCD_STSW_INIT_H +#define SCD_STSW_INIT_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_init : public scd_stsw_base +{ +public: + scd_stsw_init(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "init"; } + virtual ~scd_stsw_init() {} + + void set_connected(); + void set_failed(); + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& time); + void set_busy(); + void set_done(); + void set_fail(); + void process(); + bool active() const; + bool advance_time() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.cpp new file mode 100644 index 0000000..5fc0a51 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.cpp @@ -0,0 +1,36 @@ +#include "fsm/scd_stsw_term_ack.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_term_ack::recv_term_nack() +{ + scd_warn("received term_nack message in wrong state"); +} + + +void scd_stsw_term_ack::recv_term_ack() +{ + scd_warn("received term_nack message in wrong state"); +} + + +void scd_stsw_term_ack::send_term_nack() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_NACK); + _fsm.send_command(cmd); + _fsm.load_state(); +} + + +void scd_stsw_term_ack::send_term() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM); + _fsm.send_command(cmd); + _fsm.set_state(_st_terminate); +} + + +bool scd_stsw_term_ack::term_ack() const { return true; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.h new file mode 100644 index 0000000..19ae78d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_ack.h @@ -0,0 +1,28 @@ +#ifndef SCD_STSW_TERM_ACK_H +#define SCD_STSW_TERM_ACK_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_term_ack : public scd_stsw_base +{ +public: + scd_stsw_term_ack(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "term_ack"; } + virtual ~scd_stsw_term_ack() {} + + /* scd_stsw_base */ + void recv_term_nack(); + void recv_term_ack(); + + /* scd_cont_wrapper_if */ + void send_term_nack(); + void send_term(); + bool term_ack() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.cpp new file mode 100644 index 0000000..3bd39dc --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.cpp @@ -0,0 +1,48 @@ +#include "fsm/scd_stsw_term_req.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_term_req::recv_term_nack() +{ + _fsm.load_state(); +} + + +void scd_stsw_term_req::recv_term_ack() +{ + _fsm.set_state(_st_term_ack); +} + + +void scd_stsw_term_req::send_term_nack() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TERM_NACK); + _fsm.send_command(cmd); + _fsm.load_state(); +} + + +bool scd_stsw_term_req::term_req() const { return true; } + + +void scd_stsw_term_req::set_busy() +{ + _time_step = sc_core::SC_ZERO_TIME; + _fsm.save_state(_st_busy); +} + + +void scd_stsw_term_req::set_idle(const sc_core::sc_time& time) +{ + _time_step = time; + _fsm.save_state(_st_idle); +} + + +void scd_stsw_term_req::set_done() +{ + _fsm.save_state(_st_done); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.h new file mode 100644 index 0000000..1c39d7f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_term_req.h @@ -0,0 +1,32 @@ +#ifndef SCD_STSW_TERM_REQ_H +#define SCD_STSW_TERM_REQ_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_term_req : public scd_stsw_base +{ +public: + scd_stsw_term_req(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "term_req"; } + virtual ~scd_stsw_term_req() {} + + /* sdc_stsw_base */ + void recv_term_nack(); + void recv_term_ack(); + + /* scd_cont_wrapper_if */ + void send_term_nack(); + bool term_req() const; + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void set_done(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.cpp new file mode 100644 index 0000000..98fa64f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.cpp @@ -0,0 +1,18 @@ +#include "fsm/scd_stsw_terminate.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_terminate::set_fail() {} + + +void scd_stsw_terminate::process() +{ + if (!_fsm.is_sending()) + { + _sim.get_poller().remove_handler(_fsm); + _fsm.set_state(_st_terminated); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.h new file mode 100644 index 0000000..3671316 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminate.h @@ -0,0 +1,23 @@ +#ifndef SCD_STSW_TERMINATE_H +#define SCD_STSW_TERMINATE_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_terminate : public scd_stsw_base +{ +public: + scd_stsw_terminate(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "terminate"; } + virtual ~scd_stsw_terminate() {} + + /* scd_cont_fsm_if */ + void set_fail(); + void process(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.cpp new file mode 100644 index 0000000..8a9a045 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.cpp @@ -0,0 +1,13 @@ +#include "fsm/scd_stsw_terminated.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +void scd_stsw_terminated::set_failed() {} + + +void scd_stsw_terminated::set_fail() {} + + +bool scd_stsw_terminated::active() const { return false; } diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.h new file mode 100644 index 0000000..b0d22b1 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_terminated.h @@ -0,0 +1,25 @@ +#ifndef SCD_STSW_TERMINATED_H +#define SCD_STSW_TERMINATED_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_terminated : public scd_stsw_base +{ +public: + scd_stsw_terminated(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "terminated"; } + virtual ~scd_stsw_terminated() {} + + void set_failed(); + + /* scd_cont_fsm_if */ + void set_fail(); + bool active() const; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.cpp new file mode 100644 index 0000000..53d0a4c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.cpp @@ -0,0 +1,42 @@ +#include "fsm/scd_stsw_time_ack.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_time_ack::recv_time_nack() +{ + scd_warn("received time_nack message in wrong state"); +} + + +void scd_stsw_time_ack::recv_time_ack() +{ + scd_warn("received time_nack message in wrong state"); +} + + +void scd_stsw_time_ack::send_time_nack() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_NACK); + _fsm.send_command(cmd); + _fsm.load_state(); +} + + +void scd_stsw_time_ack::send_time(const sc_core::sc_time& time) +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME, time); + _fsm.send_command(cmd); + _fsm.set_state(_st_busy); +} + + +bool scd_stsw_time_ack::time_ack() const { return true; } + + +const sc_core::sc_time& scd_stsw_time_ack::get_time_step() +{ + return _time_step; +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.h new file mode 100644 index 0000000..a71ca7e --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_ack.h @@ -0,0 +1,29 @@ +#ifndef SCD_STSW_TIME_ACK_H +#define SCD_STSW_TIME_ACK_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_time_ack : public scd_stsw_base +{ +public: + scd_stsw_time_ack(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "time_ack"; } + virtual ~scd_stsw_time_ack() {} + + /* scd_stsw_base */ + void recv_time_nack(); + void recv_time_ack(); + + /* scd_cont_wrapper_if */ + void send_time_nack(); + void send_time(const sc_core::sc_time& time); + bool time_ack() const; + const sc_core::sc_time& get_time_step(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.cpp b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.cpp new file mode 100644 index 0000000..0336c06 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.cpp @@ -0,0 +1,48 @@ +#include "fsm/scd_stsw_time_req.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_cont_slave_wrapper.h" + + +void scd_stsw_time_req::recv_time_nack() +{ + _fsm.load_state(); +} + + +void scd_stsw_time_req::recv_time_ack() +{ + _fsm.set_state(_st_time_ack); +} + + +void scd_stsw_time_req::send_time_nack() +{ + scd_command* cmd = new scd_command(SCD_CM_CONTROL, SCD_CM_TIME_NACK); + _fsm.send_command(cmd); + _fsm.load_state(); +} + + +bool scd_stsw_time_req::time_req() const { return true; } + + +void scd_stsw_time_req::set_busy() +{ + _fsm.save_state(_st_busy); +} + + +void scd_stsw_time_req::set_idle(const sc_core::sc_time& time) +{ + _time_step = time; + _fsm.save_state(_st_idle); +} + + +void scd_stsw_time_req::set_done() +{ + _time_step = sc_core::SC_ZERO_TIME; + _fsm.save_state(_st_done); +} diff --git a/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.h b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.h new file mode 100644 index 0000000..2f45f84 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/fsm/scd_stsw_time_req.h @@ -0,0 +1,33 @@ +#ifndef SCD_STSW_TIME_REQ_H +#define SCD_STSW_TIME_REQ_H + +#include "fsm/scd_stsw_base.h" + + +/* forward declaration */ +class scd_cont_slave_wrapper; + + +class scd_stsw_time_req : public scd_stsw_base +{ +public: + scd_stsw_time_req(scd_simulator& sim, scd_cont_slave_wrapper& fsm): + scd_stsw_base(sim, fsm) { _name = "time_req"; } + virtual ~scd_stsw_time_req() {} + + /* sdc_stsw_base */ + void recv_time_nack(); + void recv_time_ack(); + + /* scd_cont_wrapper_if */ + void send_time_nack(); + bool time_req() const; + + /* scd_cont_fsm_if */ + void set_busy(); + void set_idle(const sc_core::sc_time& time); + void set_done(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_chan_man.cpp b/dol/src/dol/visitor/hdsd/scd/scd_chan_man.cpp new file mode 100644 index 0000000..d98e8ea --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_chan_man.cpp @@ -0,0 +1,216 @@ +#include "scd_chan_man.h" + +#include "scd_logging.h" + + +scd_chan_man::scd_chan_man(scd_simulator &sim): + _sim(sim), _is_ready(false) {} + + +scd_chan_man::~scd_chan_man() +{ + std::list::iterator coit; + std::list::iterator chit; + + for (coit = _connectors.begin(); coit != _connectors.end(); coit++) + delete *coit; + + for (chit = _channels.begin(); chit != _channels.end(); chit++) + delete *chit; +} + + +void scd_chan_man::register_channel(const std::string &name, sc_prim_channel& mchan) +{ + if ( _get_channel(name) != NULL ) + { + scd_warn("channel \"" + name + "\" already registered, ignoring..."); + } + else + { + scd_chan_wrapper* wrap = new scd_chan_wrapper(_sim, name, mchan); + _channels.push_back(wrap); + } + +} // register_channel() - master + + +void scd_chan_man::register_channel(const std::string &name, + sc_prim_channel& schan, const std::string &host, const uint16_t port) +{ + if ( _get_channel(name) != NULL ) + { + scd_warn("channel \"" + name + "\" already registered, ignoring..."); + } + else + { + // register channel + scd_chan_wrapper* wrap = new scd_chan_wrapper(_sim, name, schan); + _channels.push_back(wrap); + + // create connector + scd_out_connector* conn = new scd_out_connector(_sim, SCD_CM_CHANNEL, + name); + conn->connect_to(host, port); + _connectors.push_back(conn); + } + +} // register_channel() - slave + + +void scd_chan_man::init_process() +{ + if (_is_ready) + return; + + std::list::iterator coit; + + // check all connectors + for (coit = _connectors.begin(); coit != _connectors.end();) + { + scd_out_connector& conn = **coit; + + // retry to connect if necessary + conn.process(); + + if (!conn.is_connecting() && !conn.has_connection()) + { + // an error occured + scd_error("failed to connect channel \"" + + conn.get_name() + "\""); + + delete *coit; + coit = _connectors.erase(coit); + + _sim.get_cont_man().set_fail(); + return; + } + else if (!conn.is_connecting() && conn.has_connection()) + { + // channel is connected + scd_chan_wrapper& chan = *_get_channel(conn.get_name()); + + if (chan.is_initialized()) + { + scd_error("channel \"" + + conn.get_name() + "\" already connected"); + + delete *coit; + coit = _connectors.erase(coit); + + _sim.get_cont_man().set_fail(); + return; + } + else + { + scd_info("connected channel \"" + conn.get_name() + "\""); + chan.connect(conn.get_connection()); + + delete *coit; + coit = _connectors.erase(coit); + } + } // end channel connected + else + { + // still trying to connect + coit++; + } + // end this connector + } // end check all connectors + + // check if everything is initiated + _check_ready(); + +} // init_process() + + +void scd_chan_man::connect_channel(const scd_command &c, scd_socket* sock) +{ + std::string name = c.get_string(); + + scd_chan_wrapper* chan = _get_channel(name); + + if (chan == NULL) + { + scd_warn("unknown channel \"" + name + "\" supplied by peer"); + delete sock; + } + else if (chan->is_initialized()) + { + scd_warn("channel \"" + name + "\" supplied by peer already connected"); + } + else + { + scd_info("connected channel \"" + name + "\""); + chan->connect(sock); + } + +} // connect() + + +bool scd_chan_man::ready() const { return _is_ready; } + + +void scd_chan_man::process() +{ + std::list::iterator chit; + + for (chit = _channels.begin(); chit != _channels.end(); chit++) + (*chit)->process(); + +} // process() + + +void scd_chan_man::close() +{ + std::list::iterator coit; + std::list::iterator chit; + + for (coit = _connectors.begin(); coit != _connectors.end(); coit++) + (*coit)->close(); + + for (chit = _channels.begin(); chit != _channels.end(); chit++) + (*chit)->close(); + +} // close() + + +bool scd_chan_man::_check_ready() +{ + if (_is_ready) + return true; + + if (_connectors.empty()) + { + std::list::iterator chit; + + // check all channels + for (chit = _channels.begin(); chit != _channels.end(); chit++) + { + if ( !(*chit)->is_initialized() ) + return false; + } + + // all channels initiated + _is_ready = true; + return true; + } + else + return false; + +} // _check_ready() + + +scd_chan_wrapper* scd_chan_man::_get_channel(const std::string& name) +{ + std::list::iterator chit; + + for (chit = _channels.begin(); chit != _channels.end(); chit++) + { + if ( (*chit)->get_name() == name ) + return *chit; + } + + return NULL; + +} // _get_channel() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_chan_man.h b/dol/src/dol/visitor/hdsd/scd/scd_chan_man.h new file mode 100644 index 0000000..0b026cc --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_chan_man.h @@ -0,0 +1,117 @@ +#ifndef SCD_CHAN_MAN_H +#define SCD_CHAN_MAN_H + +#include +#include + +#include +using sc_core::sc_prim_channel; + +#include "scd_simulator.h" +#include "scd_command.h" +#include "scd_chan_wrapper.h" +#include "scd_out_connector.h" + + +/* forward declaration */ +class scd_out_connector; +class scd_chan_wrapper; + + +/** + * The channel manager holds all remote channels. The channels have + * to be registered before the simulator is initiated. The channel + * manager will then initiate connections to other simulators + * and will handle channels from incomming connections. + * During simulation data is sent from the channel output buffers + * to remote hosts and data is received and stored in the input buffers. + * The channel implementation will then generate events to resume + * simulation processes. + */ +class scd_chan_man +{ +public: + /** + * Constructor. + * \param sim the simulator + */ + scd_chan_man(scd_simulator &sim); + + virtual ~scd_chan_man(); + + /** + * Registers a remote channel with master endpoint on this host. + * Another host will initiate the connection and the channel will + * be connected by an in-connector calling connect_channel(). + * \param name the name of the remote channel + * \param mchan the SystemC channel implementing the + * remote-in and/or the remote-out interface. + */ + void register_channel(const std::string &name, sc_prim_channel& mchan); + + /** + * Registers a remote channel with slave endpoint on this host. + * This host will initiate the connection to the host with the + * master endpoint. To drive this process init_process() has to + * be called periodically. + * \param name the name of the remote channel + * \param mchan the SystemC channel implementing the + * remote-in and/or the remote-out interface. + * \param host the FQDN or IP address of the remote simulator + * with the master endpoint of the channel + * \param port TCP port of the remote simulator + */ + void register_channel(const std::string &name, sc_prim_channel& schan, + const std::string &host, const uint16_t port); + /** + * Drives the initialization process. Restarts outgoing connection + * attempts to connect channels to other simulators if previous + * attempts have timed out. ready() indicates the end of the + * initialization. + */ + void init_process(); + + /** + * Connects a channel from an incomming connection. Is intended to be + * called from an in-connector. + * \param c the register command received by the in-connector (contains + * the channel name) + * \param sock the socket of the incoming connection that is used + * as the data channel + */ + void connect_channel(const scd_command &c, scd_socket* sock); + + /** + * Indicates if all clients have been connected. + * \return true if the channel manager completed initialization + */ + bool ready() const; + + /** + * Checks if channels have data in the output buffers and activates + * the transmission if necessary. Receiption is resumed if the input + * buffer can accept data again. Call this function after each + * simulation step (which might fill data into the buffers that has + * to be sent). + */ + void process(); + + /** + * Closes all channels. + */ + void close(); + +private: + /* member variables */ + scd_simulator& _sim; + std::list _channels; + std::list _connectors; + + bool _is_ready; + + /* member functions */ + bool _check_ready(); + scd_chan_wrapper* _get_channel(const std::string& name); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.cpp b/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.cpp new file mode 100644 index 0000000..2629240 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.cpp @@ -0,0 +1,201 @@ +#include "scd_chan_wrapper.h" + +#include +#include + +#include "scd_logging.h" + + +scd_chan_wrapper::scd_chan_wrapper(scd_simulator& sim, const std::string& name, + sc_prim_channel& chan): + _sim(sim), _name(name), _is_initialized(false), + _is_writing(false), _is_reading(false) +{ + try + { + _chan_in = &dynamic_cast(chan); + } catch (std::bad_cast e) + { + _chan_in = NULL; + } + + try + { + _chan_out = &dynamic_cast(chan); + } catch (std::bad_cast e) + { + _chan_out = NULL; + } + + if (_chan_in == NULL && _chan_out == NULL) + scd_warn("channel \"" + _name + "\" is not a remote channel"); +} + + +scd_chan_wrapper::~scd_chan_wrapper() +{ + if ( _is_initialized ) + { + _sim.get_poller().remove_handler(*this); + _socket->close(); + delete _socket; + } +} + + +bool scd_chan_wrapper::is_initialized() const { return _is_initialized; } + + +const std::string& scd_chan_wrapper::get_name() const { return _name; } + + +void scd_chan_wrapper::connect(scd_socket* sock) +{ + assert(!_is_initialized); + + _socket = sock; + _is_initialized = true; + + sock_ev flags = SOCK_EV_CLOSE; + if (_chan_in != NULL) + { + flags |= SOCK_EV_READ; + _is_reading = true; + } + _sim.get_poller().register_handler(*this, flags); + +} // connect() + + +void scd_chan_wrapper::process() +{ + // check if channel has data to send + if (_chan_out != NULL && _is_initialized ) + { + if (!_is_writing && _socket->is_valid() && _chan_out->available() > 0 ) + { + // register write event + sock_ev events; + _sim.get_poller().get_ev(*this, events); + events |= SOCK_EV_WRITE; + _sim.get_poller().set_ev( *this, events ); + _is_writing = true; + } + } + + // check if channel has data to read + if (_chan_in != NULL && _is_initialized ) + { + if (!_is_reading && _socket->is_valid() && _chan_in->free() > 0 ) + { + // register read event + sock_ev events; + _sim.get_poller().get_ev(*this, events); + events |= SOCK_EV_READ; + _sim.get_poller().set_ev( *this, events ); + _is_reading = true; + } + } + +} // process() + + +void scd_chan_wrapper::close() +{ + if (_is_initialized) + _socket->close(); + +} // close() + + +void scd_chan_wrapper::handle_sock_ev(sock_ev events) +{ + if (events & SOCK_EV_CLOSE) + { + scd_debug("channel \"" + _name + "\" closed connection"); + _sim.get_poller().set_ev(*this, 0); + _sim.get_cont_man().set_fail(); + close(); + return; + } // end close event + + if (events & SOCK_EV_READ) + { + _read_event(); + } + + if (events & SOCK_EV_WRITE) + { + _write_event(); + } + +} // handle_sock_ev() + + +const scd_socket& scd_chan_wrapper::get_sock() { return *_socket; } + + +void scd_chan_wrapper::_read_event() +{ + size_t free = _chan_in->free(); + + /* receive portions as long as they can be received */ + size_t recv = 0xFF; //dummy + while ( free != 0 && recv != 0) + { + free = ( free < SCD_CHAN_BUFLEN ) ? free : SCD_CHAN_BUFLEN ; + recv = _socket->recv(_buf, free); + if (recv != 0) + _chan_in->receive(_buf, recv); + free = _chan_in->free(); + } + + if (free == 0) + { + sock_ev events; + _sim.get_poller().get_ev(*this, events); + events &= ~SOCK_EV_READ; + _sim.get_poller().set_ev(*this, events ); + _is_reading = false; + return; + } + + if (!_socket->is_valid()) + { + scd_debug("channel \"" + _name + "\" closed connection"); + _sim.get_poller().set_ev(*this, 0); + _sim.get_cont_man().set_fail(); + return; + } + +} // end _read_event() + + +void scd_chan_wrapper::_write_event() +{ + /* send portions as long as they can be sent */ + size_t sent = 0xFF; //dummy + while ( sent ) + { + sent = _socket->send( _chan_out->send(), _chan_out->available() ); + if (sent != 0) + _chan_out->remove(sent); + } + + if (_chan_out->available() == 0) + { + sock_ev events; + _sim.get_poller().get_ev(*this, events); + events &= ~SOCK_EV_WRITE; + _sim.get_poller().set_ev(*this, events ); + _is_writing = false; + return; + } + + if (!_socket->is_valid()) + { + scd_debug("channel \"" + _name + "\" closed connection"); + _sim.get_poller().set_ev(*this, 0); + _sim.get_cont_man().set_fail(); + } +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.h b/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.h new file mode 100644 index 0000000..44115b0 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_chan_wrapper.h @@ -0,0 +1,95 @@ +#ifndef SCD_CHAN_WRAPPER_H +#define SCD_CHAN_WRAPPER_H + +#include + +#include +using sc_core::sc_prim_channel; + +#include "scd_sock_poller.h" +#include "scd_socket.h" +#include "scd_simulator.h" +#include "scd_rem_chan_if.h" + + +/** + * Size of the input buffer. Limits the ammount of data that + * can be read from the socket in one system call. + */ +static const size_t SCD_CHAN_BUFLEN = 512; + +/* forward declaration */ +class scd_simulator; + + +/** + * Wrapper for a remote channel. Writes data from the output buffer + * of the channel to the socket. Receives data from the socket and + * writes it to the input buffer of the channel. + */ +class scd_chan_wrapper : public scd_sock_ev_handler_if +{ +public: + /** + * Constructor. + * \param sim the simulation environment + * \param name the name of the channel + * \param chan the SystemC channel implementing + * remote channel interface(s) + */ + scd_chan_wrapper(scd_simulator& sim, const std::string& name, + sc_prim_channel& chan); + + virtual ~scd_chan_wrapper(); + + /** + * Indicates if a socket has been set for this channel. + * \return true if a socket has been set + */ + bool is_initialized() const; + + /** + * Returns the name of the channel. + */ + const std::string& get_name() const; + + /** + * Sets the socket of the channel. + */ + void connect(scd_socket* sock); + + /** + * Checks if data has to be send or can be received again and activates + * transmission if necessary. Call this function after every simulation + * step. + */ + void process(); + + /** + * Closes the connection to the peer. + */ + void close(); + + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket &get_sock(); + +private: + scd_simulator& _sim; + std::string _name; + bool _is_initialized; + + scd_socket* _socket; + scd_rem_chan_in_if* _chan_in; + scd_rem_chan_out_if* _chan_out; + char _buf[SCD_CHAN_BUFLEN]; + bool _is_writing; + bool _is_reading; + + /* member functions */ + void _write_event(); + void _read_event(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command.cpp b/dol/src/dol/visitor/hdsd/scd/scd_command.cpp new file mode 100644 index 0000000..008171d --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command.cpp @@ -0,0 +1,105 @@ +#include "scd_command.h" + +#include "cstring" +#include "arpa/inet.h" + + +scd_command::scd_command(): _type(0), _subtype(0), _msglen(0) +{ + _msg = NULL; +} + + +scd_command::scd_command(uint16_t type, uint16_t subtype): + _type(type), _subtype(subtype), _msglen(0) +{ + _msg = NULL; +} + + +scd_command::scd_command(uint16_t type, uint16_t subtype, + const std::string& msg): + _type(type), _subtype(subtype) +{ + if (msg.length() > 0) + { + _msglen = msg.length()+1; + _msg = new char[_msglen]; + strncpy(_msg, msg.c_str(), _msglen); + _msg[_msglen-1] = 0; + } + else + { + _msglen = 0; + _msg = NULL; + } +} + + +scd_command::scd_command(uint16_t type, uint16_t subtype, + const sc_core::sc_time& time) : _type(type), _subtype(subtype) +{ + uint64_t value; + uint32_t hi, lo; + + _msglen = 8; + _msg = new char[_msglen]; + + // obtain high and low words + value = time.value(); + lo = value & 0xFFFFFFFF; + value >>= 32; + hi = value & 0xFFFFFFFF; + + // do network conversion + hi = htonl(hi); + lo = htonl(lo); + + // store to buffer + memcpy(_msg, &hi, 4); + memcpy(_msg + 4, &lo, 4); +} + + + +scd_command::~scd_command() +{ + if (_msg != NULL) + delete _msg; +} + + +uint16_t scd_command::get_type() const { return _type; } + + +uint16_t scd_command::get_subtype() const { return _subtype; } + + +std::string scd_command::get_string() const +{ + if (_msglen <= 1) + return std::string(); + else + return std::string(_msg, _msglen-1); +} + + +sc_core::sc_time scd_command::get_time() const +{ + if (_msglen != 8) + return sc_core::SC_ZERO_TIME; + + uint64_t value; + uint32_t hi, lo; + + // get value from buffer + memcpy(&hi, _msg, 4); + memcpy(&lo, _msg+4, 4); + + // do network conversion + value = ntohl(hi); + value <<= 32; + value |= ntohl(lo); + + return sc_core::sc_time(value, false); +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command.h b/dol/src/dol/visitor/hdsd/scd/scd_command.h new file mode 100644 index 0000000..cbb3bed --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command.h @@ -0,0 +1,108 @@ +#ifndef SCD_COMMAND_H +#define SCD_COMMAND_H + +#include +#include +#include + +#include "systemc" + + +const size_t SCD_CM_MAXLEN = 512; +const size_t SCD_CM_HEADER = 3*2; + +/* command types */ +const uint16_t SCD_CM_REGISTER = 1; +const uint16_t SCD_CM_CONFIG = 2; +const uint16_t SCD_CM_CONTROL = 3; + +/* command subtypes */ +// register +const uint16_t SCD_CM_NETSIM = 1; +const uint16_t SCD_CM_CHANNEL = 2; +// control +const uint16_t SCD_CM_BUSY = 1; +const uint16_t SCD_CM_IDLE = 2; +const uint16_t SCD_CM_DONE = 3; +const uint16_t SCD_CM_FAILED = 4; +const uint16_t SCD_CM_TIME_REQ = 5; +const uint16_t SCD_CM_TIME_ACK = 6; +const uint16_t SCD_CM_TIME_NACK = 7; +const uint16_t SCD_CM_TIME = 8; +const uint16_t SCD_CM_TERM_REQ = 9; +const uint16_t SCD_CM_TERM_ACK = 10; +const uint16_t SCD_CM_TERM_NACK = 11; +const uint16_t SCD_CM_TERM = 12; + +/* forward declarations */ +class scd_command_reader; +class scd_command_writer; + + +/** + * Command class. Commands are control messages that are sent between + * the different simulators. A command has a type, a subtype and a potential + * message part. + */ +class scd_command +{ +friend class scd_command_reader; +friend class scd_command_writer; + +public: + /** + * Default constructor. Creates an empty command. + */ + scd_command(); + + /* + * Constructor. Creates a command with no message part. + * \param type the type of this command + * \param subtype the subtype of this command + */ + scd_command(uint16_t type, uint16_t subtype); + + /** + * Constructor. Creates a new command with a string as message + * part. + */ + scd_command(uint16_t type, uint16_t subtype, const std::string& msg); + + /** + * Constructor. Creates a new command with a SystemC time value as + * message part. + */ + scd_command(uint16_t type, uint16_t subtype, const sc_core::sc_time& time); + + virtual ~scd_command(); + + /** + * Returns the type of the command. + */ + uint16_t get_type() const; + + /** + * Returns the subtype of the command. + */ + uint16_t get_subtype() const; + + /** + * Returns the message part interpreted as a string. + */ + std::string get_string() const; + + /** + * Returns the message part interpreted as a SystemC time. + * If the message part does not have the correct size SC_ZERO_TIME + * is returned instead. + */ + sc_core::sc_time get_time() const; + +private: + uint16_t _type; + uint16_t _subtype; + uint16_t _msglen; + char* _msg; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command_reader.cpp b/dol/src/dol/visitor/hdsd/scd/scd_command_reader.cpp new file mode 100644 index 0000000..7c9d904 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command_reader.cpp @@ -0,0 +1,119 @@ +#include "scd_command_reader.h" + +#include + +#include "scd_logging.h" + + +scd_command_reader::scd_command_reader(): + _command(NULL), _is_reading(false), _success(false) {} + + +scd_command_reader::~scd_command_reader() +{ + if (_is_reading || _success) + { + delete _command; + } +} + + +void scd_command_reader::set_socket(scd_socket& sock) { _socket = &sock; } + + +void scd_command_reader::read() +{ + if (!_is_reading) + { + if (_success) + return; + + _is_reading = true; + _header_read = false; + _remaining = SCD_CM_HEADER; + _off = 0; + _command = new scd_command; + } + + // receive header + if (!_header_read) + { + // read header + size_t read = _socket->recv(_header_buf + _off, _remaining); + _remaining -= read; + _off += read; + + if (_remaining == 0) + { + // read values from buffer + uint16_t* intbuf = reinterpret_cast(_header_buf); + _command->_type = ntohs( intbuf[0] ); + _command->_subtype = ntohs( intbuf[1] ); + _command->_msglen = ntohs( intbuf[2] ); + + _header_read = true; + + // allocate buffer for further receiption + if (_command->_msglen > 0) + { + if (_command->_msglen > SCD_CM_MAXLEN) + { + // illegal command + _command->_msglen = 0; + delete _command; + _is_reading = false; + } + else + { + _command->_msg = new char[_command->_msglen]; + _remaining = _command->_msglen; + _off = 0; + } + } + else + { + // command without payload + _command->_msglen = 0; // to prevent negative values + _remaining = 0; + _is_reading = false; + _success = true; + } + } + } + + // receive msg + if (_header_read && _is_reading) + { + size_t read = _socket->recv(_command->_msg + _off, _remaining); + _remaining -= read; + _off += read; + + if (_remaining == 0) + { + _is_reading = false; + _success = true; + } + } + +} // read() + + +bool scd_command_reader::is_reading() { return _is_reading; } + + +bool scd_command_reader::has_command() { return _success; } + + +scd_command* scd_command_reader::get_command() +{ + if (!_success) + { + return NULL; + } + else + { + _success = false; + return _command; + } + +} // get_command() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command_reader.h b/dol/src/dol/visitor/hdsd/scd/scd_command_reader.h new file mode 100644 index 0000000..db06830 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command_reader.h @@ -0,0 +1,64 @@ +#ifndef SCD_COMMAND_READER_H +#define SCD_COMMAND_READER_H + +#include "scd_command.h" +#include "scd_socket.h" + +/** + * Reads commands from a socket. The receiption does not have to complete + * withing one call. As long as it is ongoing the socket shall not be read + * by the application. + */ +class scd_command_reader +{ +public: + scd_command_reader(); + + virtual ~scd_command_reader(); + + /** + * Sets the socket to read from. Do not try to read before setting the + * socket. + */ + void set_socket(scd_socket &sock); + + /** + * Initiates reading a command if it is not reading yet or continues + * to read a previously unfinished command. + */ + void read(); + + /** + * Indicates if a command is being read. This is the case between calling + * read() and the completion of the command (or failure if an illegal + * command was received). + * \return true if a command is currently being received + */ + bool is_reading(); + + /** + * Indicates if a command has been received successfully and is ready + * to be picked up. + * \return true if a command can be collected + */ + bool has_command(); + + /** + * Returns a successfully received command. The application has to free + * it by itself. The reader does not keep a reference. + * \return the command or NULL if has_command() is false + */ + scd_command* get_command(); + +private: + scd_socket* _socket; + scd_command* _command; + bool _is_reading; + bool _header_read; + bool _success; + size_t _remaining; + size_t _off; + char _header_buf[SCD_CM_HEADER]; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command_writer.cpp b/dol/src/dol/visitor/hdsd/scd/scd_command_writer.cpp new file mode 100644 index 0000000..64193ff --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command_writer.cpp @@ -0,0 +1,93 @@ +#include "scd_command_writer.h" + +#include + +#include "scd_logging.h" + + +scd_command_writer::scd_command_writer(): + _is_writing(false), _remaining(0) {} + + +scd_command_writer::~scd_command_writer() +{ + std::list::iterator iter; + for ( iter = _commands.begin(); iter != _commands.end(); iter++) + delete *iter; +} + + +void scd_command_writer::set_socket(scd_socket& sock) { _socket = &sock; } + + +bool scd_command_writer::is_writing() const { return _is_writing; } + + +bool scd_command_writer::queue_command(scd_command* cmd) +{ + if (cmd->_msglen <= SCD_CM_MAXLEN) + { + _commands.push_back(cmd); + _is_writing = true; + return true; + } + else + { + scd_warn("command too long"); + delete cmd; + return false; + } +} + +void scd_command_writer::write() +{ + bool finishedmsg; + do + { + finishedmsg = _send_cmd(); + } + while (finishedmsg); + +} // write() + + +inline bool scd_command_writer::_send_cmd() +{ + if (!_is_writing) + return false; + + if (_remaining == 0) + { + /* fetch next command */ + scd_command* cmd = _commands.front(); + _commands.pop_front(); + + // store header to buffer + uint16_t* intbuf = reinterpret_cast(_buf); + intbuf[0] = htons(cmd->_type); + intbuf[1] = htons(cmd->_subtype); + intbuf[2] = htons(cmd->_msglen); + + // store message to buffer + memcpy(_buf + SCD_CM_HEADER, cmd->_msg, cmd->_msglen); + + _remaining = SCD_CM_HEADER + cmd->_msglen; + _off = 0; + delete cmd; + } + + size_t sent = _socket->send(_buf + _off, _remaining); + + _off += sent; + _remaining -= sent; + + if (_remaining == 0) + { + if (_commands.empty()) + _is_writing = false; + return true; + } + else + return false; + +} // _send_cmd() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_command_writer.h b/dol/src/dol/visitor/hdsd/scd/scd_command_writer.h new file mode 100644 index 0000000..a8bb427 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_command_writer.h @@ -0,0 +1,61 @@ +#ifndef SCD_COMMAND_WRITER_H +#define SCD_COMMAND_WRITER_H + +#include + +#include "scd_command.h" +#include "scd_socket.h" + + +/** + * Queues multiple commands and writes then to a socket continuously. + */ +class scd_command_writer +{ +public: + scd_command_writer(); + + virtual ~scd_command_writer(); + + /** + * Sets the socket to write to. Do not try to write before + * setting the socket. + */ + void set_socket(scd_socket& sock); + + /** + * Indicates if the writer is currently sending a command. + * \return true if the writer is busy sending commands + */ + bool is_writing() const; + + /** + * Queues a command to be sent. Commands will be sent out in FIFO + * manner. Sending is not initiated by this call. + * \param message to send + * \return false if the message is longet than SCD_CM_MAXLEN. In this + * case the message is destroyed. + */ + bool queue_command(scd_command* cmd); + + /** + * Write commands or part of commands to the socket. As many queued + * commands as possible are sent but none has to be finished. + * \exception scd_exception if unexpected errors occured + */ + void write(); + +private: + scd_socket* _socket; + bool _is_writing; + + std::list_commands; + char _buf[SCD_CM_HEADER + SCD_CM_MAXLEN]; + size_t _remaining; + size_t _off; + + inline bool _send_cmd(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_man.h b/dol/src/dol/visitor/hdsd/scd/scd_cont_man.h new file mode 100644 index 0000000..5499984 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_man.h @@ -0,0 +1,38 @@ +#ifndef SCD_CONT_MAN_H +#define SCD_CONT_MAN_H + +#include + +#include "fsm/scd_cont_fsm_if.h" + + +const bool SCD_MASTER = true; +const bool SCD_SLAVE = false; + +const int SCD_CONT_DELAY = 20; + +/** + * Control manager. Shall be set up before the simulation is initialized. + */ +class scd_cont_man : public scd_cont_fsm_if +{ +public: + virtual ~scd_cont_man() {}; + + /** + * Sets the master simulator for a slave. + * \param host the FQDN or IP of the master simulator + * \param port the TCP port of the master simulator + * \exception scd_exception if the simulator is not a slave + */ + virtual void set_master(const std::string& host, uint16_t port) = 0; + + /** + * Registers a slave simulator for the master. + * \param name the name of the slave + * \exception scd_exception if the simulator is not the master + */ + virtual void register_slave(const std::string& name) = 0; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.cpp b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.cpp new file mode 100644 index 0000000..932644c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.cpp @@ -0,0 +1,93 @@ +#include "scd_cont_man_master.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +scd_cont_man_master::scd_cont_man_master(scd_simulator& sim): + _sim(sim), scd_cont_fsm("master"), + _st_init(sim, *this), _st_busy(sim, *this), _st_idle(sim, *this), + _st_done(sim, *this), _st_time_req(sim, *this), _st_time(sim, *this), + _st_term_req(sim, *this), _st_terminate(sim, *this), + _st_terminated(sim, *this), _st_fail(sim, *this), _st_failed(sim, *this) +{ + // set initial state + set_state(_st_init); +} + + +scd_cont_man_master::~scd_cont_man_master() +{ + std::list::iterator it; + + for (it = _slaves.begin(); it != _slaves.end(); it++) + delete *it; +} + + +void scd_cont_man_master::connect_slave(const scd_command &c, scd_socket* sock) +{ + std::string name = c.get_string(); + + scd_cont_slave_wrapper* slave = _get_slave(name); + + if (slave == NULL) + { + scd_warn("unknown slave \"" + name + "\""); + delete sock; + } + else + slave->connect(sock); + +} // connect() + + +void scd_cont_man_master::set_master(const std::string& host, uint16_t port) +{ + scd_error("simulator is master, can not have another master"); + throw scd_exception("simulator is master"); +} // set_master() + + +void scd_cont_man_master::register_slave(const std::string& name) +{ + if ( _get_slave(name) != NULL ) + { + scd_warn("slave \"" + name + "\" already registered, ignoring..."); + } + else + { + scd_cont_slave_wrapper* wrap; + wrap = new scd_cont_slave_wrapper(_sim, name); + _slaves.push_back(wrap); + } + +} // register_slave + + +void scd_cont_man_master::process() +{ + std::list::iterator it; + + // process all slaves + for (it = _slaves.begin(); it != _slaves.end(); it++) + (*it)->process(); + + // process the local state + _state->process(); +} + + +scd_cont_slave_wrapper* scd_cont_man_master::_get_slave(const std::string& name) +{ + std::list::iterator it; + + for (it = _slaves.begin(); it != _slaves.end(); it++) + { + if ( (*it)->get_name() == name ) + return *it; + } + + return NULL; + +} // _get_slave() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.h b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.h new file mode 100644 index 0000000..f240321 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_master.h @@ -0,0 +1,86 @@ +#ifndef SCD_CONT_MAN_MASTER_H +#define SCD_CONT_MAN_MASTER_H + +#include + +#include "scd_simulator.h" +#include "scd_cont_man.h" +#include "scd_cont_slave_wrapper.h" +#include "fsm/scd_cont_fsm.h" + +#include "fsm/scd_stm_init.h" +#include "fsm/scd_stm_busy.h" +#include "fsm/scd_stm_idle.h" +#include "fsm/scd_stm_done.h" +#include "fsm/scd_stm_time_req.h" +#include "fsm/scd_stm_time.h" +#include "fsm/scd_stm_term_req.h" +#include "fsm/scd_stm_terminate.h" +#include "fsm/scd_stm_terminated.h" +#include "fsm/scd_stm_fail.h" +#include "fsm/scd_stm_failed.h" + +/** + * Control manager for the master. Waits for all slaves to connect. + * Synchronizes the global simulation state. + */ +class scd_cont_man_master : public scd_cont_man, public scd_cont_fsm +{ + friend class scd_stm_base; + +public: + /** + * Constructor. + * \param sim the simulation environment + */ + scd_cont_man_master(scd_simulator& sim); + + virtual ~scd_cont_man_master(); + + /** + * Connects a slave. Is called from an in-connector. + * \param c the register command that has been received by the in-connector + * (contains the slave name). + * \param sock the incomming connection + */ + void connect_slave(const scd_command& c, scd_socket* sock); + + /* scd_cont_man */ + void set_master(const std::string& host, uint16_t port); + void register_slave(const std::string& name); + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& t) { return _state->set_idle(t); } + void set_busy() { return _state->set_busy(); } + void set_done() { return _state->set_done(); } + void set_fail() { return _state->set_fail(); } + void process(); + bool active() const { return _state->active(); } + bool busy() const { return _state->busy(); } + bool failed() const { return _state->failed(); } + bool advance_time() const { return _state->advance_time(); } + const sc_core::sc_time& get_time_step() { return _state->get_time_step(); } + +private: + scd_simulator& _sim; + std::list _slaves; + sc_core::sc_time _time_step; + + /* states */ + scd_stm_init _st_init; + scd_stm_busy _st_busy; + scd_stm_idle _st_idle; + scd_stm_done _st_done; + scd_stm_time_req _st_time_req; + scd_stm_time _st_time; + scd_stm_term_req _st_term_req; + scd_stm_terminate _st_terminate; + scd_stm_terminated _st_terminated; + scd_stm_fail _st_fail; + scd_stm_failed _st_failed; + + /* member functions */ + scd_cont_slave_wrapper* _get_slave(const std::string& name); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.cpp b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.cpp new file mode 100644 index 0000000..7368ef9 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.cpp @@ -0,0 +1,181 @@ +#include "scd_cont_man_slave.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +scd_cont_man_slave::scd_cont_man_slave(scd_simulator& sim): + scd_cont_fsm("slave"), _sim(sim), + _connector(sim, SCD_CM_NETSIM, sim.get_name()), _has_master(false), + _st_init(sim, *this), _st_busy(sim, *this), _st_fail(sim, *this), + _st_failed(sim, *this), _st_idle(sim, *this), _st_done(sim, *this), + _st_time_ack(sim, *this), _st_time(sim, *this), _st_term_ack(sim, *this), + _st_terminated(sim, *this) +{ + // set initial state + set_state(_st_init); +} + + +scd_cont_man_slave::~scd_cont_man_slave() +{ + if (_socket != NULL) + delete _socket; +} + + +void scd_cont_man_slave::set_socket() +{ + _socket = _connector.get_connection(); + _writer.set_socket(*_socket); + _reader.set_socket(*_socket); +} // set_socket() + + +void scd_cont_man_slave::send_command(scd_command* cmd) +{ + // register write event if not registered + if (!_writer.is_writing() && _socket->is_valid()) + _sim.get_poller().set_ev(*this, SOCK_EV_WRITE | SOCK_EV_READ + | SOCK_EV_CLOSE); + + // queue command to be written + _writer.queue_command(cmd); +} + + +bool scd_cont_man_slave::is_sending() +{ + return _writer.is_writing() && _socket->is_valid(); +} + + +void scd_cont_man_slave::close() +{ + if (_socket != NULL) + _socket->close(); +} + + +void scd_cont_man_slave::set_master(const std::string& host, uint16_t port) +{ + if (!_has_master) + _connector.connect_to(host, port); + else + { + scd_error("master already set"); + throw scd_exception("master already set"); + } +} // set_master() + + +void scd_cont_man_slave::register_slave(const std::string& name) +{ + scd_error("simulator is slave, can not have other slaves"); + throw scd_exception("simulator is slave"); + +} // register_slave + + +void scd_cont_man_slave::handle_sock_ev(sock_ev events) +{ + scd_sts_base& state = *static_cast(_state); + + if (events & SOCK_EV_CLOSE) + { + scd_error("connection to master lost"); + _socket->close(); + state.set_failed(); + return; + } + + if (events & SOCK_EV_READ) + { + _reader.read(); + + if (!_socket->is_valid()) + { + // connection closed + scd_error("connection to master lost"); + state.set_failed(); + return; + } + + if (!_reader.is_reading() && !_reader.has_command()) + { + // received an illegal command + scd_error("received illegal command"); + state.set_fail(); + return; + } + + if (_reader.has_command()) + { + scd_command* cmd = _reader.get_command(); + _process_cmd(*cmd); + delete cmd; + } + + } // end read event + + if (events & SOCK_EV_WRITE) + { + _writer.write(); + if (!_writer.is_writing()) + { + // done sending commands + _sim.get_poller().set_ev(*this, SOCK_EV_READ | SOCK_EV_CLOSE); + } + } // end write event + + if (!_socket->is_valid()) + { + scd_error("connection to master lost"); + state.set_failed(); + } + +} // handle_sock_ev() + + +const scd_socket& scd_cont_man_slave::get_sock() { return *_socket; } + + +void scd_cont_man_slave::_process_cmd(scd_command& cmd) +{ + if (cmd.get_type() != SCD_CM_CONTROL) + { + scd_warn("received non-control command"); + return; + } + + scd_sts_base* state = static_cast(_state); + + switch (cmd.get_subtype()) + { + case SCD_CM_FAILED: + scd_error("master failed"); + state->set_failed(); + break; + case SCD_CM_TIME_REQ: + state->recv_time_req(); + break; + case SCD_CM_TIME_NACK: + state->recv_time_nack(); + break; + case SCD_CM_TIME: + state->recv_time(cmd.get_time()); + break; + case SCD_CM_TERM_REQ: + state->recv_term_req(); + break; + case SCD_CM_TERM_NACK: + state->recv_term_nack(); + break; + case SCD_CM_TERM: + state->recv_term(); + break; + default: + scd_warn("received unknown command"); + break; + } +} // _process_cmd() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.h b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.h new file mode 100644 index 0000000..4156f7c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_man_slave.h @@ -0,0 +1,111 @@ +#ifndef SCD_CONT_MAN_SLAVE_H +#define SCD_CONT_MAN_SLAVE_H + +#include "scd_simulator.h" +#include "scd_socket.h" +#include "scd_out_connector.h" +#include "scd_command_writer.h" +#include "scd_command_reader.h" +#include "scd_cont_man.h" +#include "scd_sock_poller.h" + +#include "fsm/scd_cont_fsm.h" +#include "fsm/scd_sts_init.h" +#include "fsm/scd_sts_busy.h" +#include "fsm/scd_sts_idle.h" +#include "fsm/scd_sts_done.h" +#include "fsm/scd_sts_time_ack.h" +#include "fsm/scd_sts_time.h" +#include "fsm/scd_sts_term_ack.h" +#include "fsm/scd_sts_terminated.h" +#include "fsm/scd_sts_fail.h" +#include "fsm/scd_sts_failed.h" + +/** + * Control manager for a slave. Connects to the master controller and + * synchronizes the local and global simulation state. + */ +class scd_cont_man_slave : public scd_cont_man, public scd_cont_fsm, + public scd_sock_ev_handler_if +{ + friend class scd_sts_base; + +public: + /** + * Constructor. + * \param sim the simulation environment + */ + scd_cont_man_slave(scd_simulator& sim); + + virtual ~scd_cont_man_slave(); + + /** + * Sets the socket where to read from and write to after the connection + * has been established to the master. + */ + void set_socket(); + + /** + * Sends a command to the master. + * \param cmd the command to send + */ + void send_command(scd_command* cmd); + + /** + * Indicates if a command is being sent to the master (and did not finish + * yet). + * \return true if a command is currently being sent + */ + bool is_sending(); + + /** + * Closes the connection to the master controller. + */ + void close(); + + /* scd_cont_man */ + void set_master(const std::string& host, uint16_t port); + void register_slave(const std::string& name); + + /* scd_cont_fsm_if */ + void set_idle(const sc_core::sc_time& t) { return _state->set_idle(t); } + void set_busy() { return _state->set_busy(); } + void set_done() { return _state->set_done(); } + void set_fail() { return _state->set_fail(); } + void process() { return _state->process(); } + bool active() const { return _state->active(); } + bool busy() const { return _state->busy(); } + bool failed() const { return _state->failed(); } + bool advance_time() const { return _state->advance_time(); } + const sc_core::sc_time& get_time_step() { return _state->get_time_step(); } + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket& get_sock(); + +private: + scd_simulator& _sim; + + scd_socket* _socket; + scd_out_connector _connector; + scd_command_writer _writer; + scd_command_reader _reader; + bool _has_master; + + /* states */ + scd_sts_init _st_init; + scd_sts_busy _st_busy; + scd_sts_idle _st_idle; + scd_sts_done _st_done; + scd_sts_time_ack _st_time_ack; + scd_sts_time _st_time; + scd_sts_term_ack _st_term_ack; + scd_sts_terminated _st_terminated; + scd_sts_fail _st_fail; + scd_sts_failed _st_failed; + + /* member functions */ + void _process_cmd(scd_command& cmd); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.cpp b/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.cpp new file mode 100644 index 0000000..0bc710a --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.cpp @@ -0,0 +1,182 @@ +#include "scd_cont_slave_wrapper.h" + +#include "scd_logging.h" +#include "scd_exception.h" + + +scd_cont_slave_wrapper::scd_cont_slave_wrapper(scd_simulator& sim, + const std::string& name): + _sim(sim), scd_cont_fsm(name), _name(name), _is_connected(false), + _st_init(sim, *this), _st_busy(sim, *this), _st_idle(sim, *this), + _st_done(sim, *this), _st_time_req(sim, *this), _st_time_ack(sim, *this), + _st_term_req(sim, *this), _st_term_ack(sim, *this), + _st_terminate(sim, *this), _st_terminated(sim, *this), _st_fail(sim, *this), + _st_failed(sim, *this) +{ + // set initial state + set_state(_st_init); +} + + +scd_cont_slave_wrapper::~scd_cont_slave_wrapper() +{ + if (_socket != NULL) + delete _socket; +} + + +const std::string& scd_cont_slave_wrapper::get_name() const { return _name; } + + +void scd_cont_slave_wrapper::connect(scd_socket* sock) +{ + if (!_is_connected) + { + _socket = sock; + _writer.set_socket(*_socket); + _reader.set_socket(*_socket); + _is_connected = true; + scd_info("slave \"" + _name + "\" connected"); + static_cast(_state)->set_connected(); + } + else + { + scd_warn("slave " + _name + " has already been connected"); + delete sock; + } +} // connect() + + +void scd_cont_slave_wrapper::send_command(scd_command* cmd) +{ + // register write event if not registered + if (!_writer.is_writing() && _is_connected && _socket->is_valid()) + _sim.get_poller().set_ev(*this, SOCK_EV_WRITE | SOCK_EV_READ + | SOCK_EV_CLOSE); + + // queue command to be written + _writer.queue_command(cmd); +} + + +bool scd_cont_slave_wrapper::is_sending() const +{ + return _writer.is_writing() && _is_connected && _socket->is_valid(); +} + + +void scd_cont_slave_wrapper::close() +{ + if (_is_connected) + _socket->close(); +} + + +void scd_cont_slave_wrapper::handle_sock_ev(sock_ev events) +{ + scd_stsw_base& state = *static_cast(_state); + + if (events & SOCK_EV_CLOSE) + { + _socket->close(); + scd_error("connection to slave \"" + _name + "\" lost"); + state.set_failed(); + return; + } + + if (events & SOCK_EV_READ) + { + _reader.read(); + + if (!_socket->is_valid()) + { + // connection closed + scd_error("connection to slave \"" + _name + "\" lost"); + state.set_failed(); + return; + } + + if (!_reader.is_reading() && !_reader.has_command()) + { + // received an illegal command + scd_error("received illegal command"); + state.set_fail(); + return; + } + + if (_reader.has_command()) + { + scd_command* cmd = _reader.get_command(); + _process_cmd(*cmd); + delete cmd; + } + + } // end read event + + if (events & SOCK_EV_WRITE) + { + _writer.write(); + + if (!_writer.is_writing()) + { + // done sending commands + _sim.get_poller().set_ev(*this, SOCK_EV_READ | SOCK_EV_CLOSE); + } + + if (!_socket->is_valid()) + { + scd_error("connection to slave \"" + _name + "\" lost"); + state.set_failed(); + return; + } + } // end write event + + +} // handle_sock_ev() + + +const scd_socket& scd_cont_slave_wrapper::get_sock() { return *_socket; } + + +void scd_cont_slave_wrapper::_process_cmd(const scd_command& cmd) +{ + if (cmd.get_type() != SCD_CM_CONTROL) + { + scd_warn("received non-control command"); + return; + } + + scd_stsw_base* state = static_cast(_state); + + switch(cmd.get_subtype()) + { + case SCD_CM_BUSY: + state->set_busy(); + break; + case SCD_CM_IDLE: + state->set_idle(cmd.get_time()); + break; + case SCD_CM_DONE: + state->set_done(); + break; + case SCD_CM_TIME_NACK: + state->recv_time_nack(); + break; + case SCD_CM_TIME_ACK: + state->recv_time_ack(); + break; + case SCD_CM_TERM_NACK: + state->recv_term_nack(); + break; + case SCD_CM_TERM_ACK: + state->recv_term_ack(); + break; + case SCD_CM_FAILED: + scd_error("slave \"" + _name + "\" failed"); + state->set_failed(); + break; + default: + scd_warn("received unknown command"); + break; + } +} // _process_cmd() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.h b/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.h new file mode 100644 index 0000000..882f9ac --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_cont_slave_wrapper.h @@ -0,0 +1,142 @@ +#ifndef SCD_CONT_SLAVE_WRAPPER_H +#define SCD_CONT_SLAVE_WRAPPER_H + +#include "scd_simulator.h" +#include "scd_socket.h" +#include "scd_command_writer.h" +#include "scd_command_reader.h" +#include "scd_cont_man.h" +#include "scd_sock_poller.h" +#include "fsm/scd_cont_fsm.h" +#include "fsm/scd_cont_wrapper_if.h" + +#include "fsm/scd_stsw_init.h" +#include "fsm/scd_stsw_busy.h" +#include "fsm/scd_stsw_idle.h" +#include "fsm/scd_stsw_done.h" +#include "fsm/scd_stsw_time_req.h" +#include "fsm/scd_stsw_time_ack.h" +#include "fsm/scd_stsw_term_req.h" +#include "fsm/scd_stsw_term_ack.h" +#include "fsm/scd_stsw_terminate.h" +#include "fsm/scd_stsw_terminated.h" +#include "fsm/scd_stsw_fail.h" +#include "fsm/scd_stsw_failed.h" + +/** + * Control manager wrapper for a slave. Reflects the masters view of the + * slaves state. + */ +class scd_cont_slave_wrapper : public scd_cont_fsm_if, public scd_cont_fsm, + public scd_cont_wrapper_if, public scd_sock_ev_handler_if +{ + friend class scd_stsw_base; + +public: + /** + * Constructor. + * \param sim the simulation environment + * \param name the name of the slave + */ + scd_cont_slave_wrapper(scd_simulator& sim, const std::string& name); + + virtual ~scd_cont_slave_wrapper(); + + /** + * Returns the name of the slave. + */ + const std::string& get_name() const; + + /** + * Sets the socket of the slave. + * \param sock the connection to the slave + */ + void connect(scd_socket* sock); + + /** + * Sends a command to the slave. + */ + void send_command(scd_command* cmd); + + /** + * Indicates if a command is being sent to the slave. + * \return true if a command is still being sent + */ + bool is_sending() const; + + /** + * Closes the connection to the slave. + */ + void close(); + + /* scd_cont_slave_wrapper_if */ + void send_time_req() + { return static_cast(_state)->send_time_req(); } + void send_time_nack() + { return static_cast(_state)->send_time_nack(); } + void send_time(const sc_core::sc_time& time) + { return static_cast(_state)->send_time(time); } + void send_term_req() + { return static_cast(_state)->send_term_req(); } + void send_term_nack() + { return static_cast(_state)->send_term_nack(); } + void send_term() + { return static_cast(_state)->send_term(); } + bool time_req() const + { return static_cast(_state)->time_req(); } + bool time_ack() const + { return static_cast(_state)->time_ack(); } + bool term_req() const + { return static_cast(_state)->term_req(); } + bool term_ack() const + { return static_cast(_state)->term_ack(); } + bool idle() const + { return static_cast(_state)->idle(); } + bool done() const + { return static_cast(_state)->done(); } + + /* scd_cont_fsm_if */ + void set_busy() { return _state->set_busy(); } + void set_idle(const sc_core::sc_time& t) { return _state->set_idle(t); } + void set_done() { return _state->set_done(); } + void set_fail() { return _state->set_fail(); } + void process() { return _state->process(); } + bool active() const { return _state->active(); } + bool busy() const { return _state->busy(); } + bool failed() const { return _state->failed(); } + bool advance_time() const { return _state->advance_time(); } + const sc_core::sc_time& get_time_step() { return _state->get_time_step(); } + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket& get_sock(); + +private: + scd_simulator& _sim; + std::string _name; + bool _is_connected; + + scd_socket* _socket; + scd_command_writer _writer; + scd_command_reader _reader; + sc_core::sc_time _time_step; + + /* states */ + scd_stsw_init _st_init; + scd_stsw_busy _st_busy; + scd_stsw_idle _st_idle; + scd_stsw_done _st_done; + scd_stsw_time_req _st_time_req; + scd_stsw_time_ack _st_time_ack; + scd_stsw_term_req _st_term_req; + scd_stsw_term_ack _st_term_ack; + scd_stsw_terminate _st_terminate; + scd_stsw_terminated _st_terminated; + scd_stsw_fail _st_fail; + scd_stsw_failed _st_failed; + + /* member functions */ + void _process_cmd(const scd_command& cmd); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_exception.cpp b/dol/src/dol/visitor/hdsd/scd/scd_exception.cpp new file mode 100644 index 0000000..f5eb4f8 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_exception.cpp @@ -0,0 +1,35 @@ +#include "scd_exception.h" + + +scd_exception::scd_exception(const std::string& msg): _msg(msg) +{ + _get_backtrace(); +} + + +scd_exception::scd_exception(const std::string& msg, int errn) +{ + _msg = msg + ": " + std::string(strerror(errn)); + _get_backtrace(); +} + + +const char* scd_exception::what() throw() { return _msg.c_str(); } + + +const char* scd_exception::stacktrace() { return _backtrace.c_str(); } + + +void scd_exception::_get_backtrace() +{ + void* traces[SCD_EX_TRACES]; + int num_traces = backtrace(traces, SCD_EX_TRACES); + char ** symbols = backtrace_symbols(traces, num_traces); + for (int i = 0; i < num_traces; i++) + { + _backtrace += std::string(symbols[i]); + _backtrace += "\r\n"; + } + + free(symbols); +} // _obtain_backtrace() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_exception.h b/dol/src/dol/visitor/hdsd/scd/scd_exception.h new file mode 100644 index 0000000..aa12e28 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_exception.h @@ -0,0 +1,57 @@ +#ifndef SCD_EXCEPTION_H +#define SCD_EXCEPTION_H + +#include +#include +#include + + +/** + * Maximum level of stacktrace. + */ +const int SCD_EX_TRACES = 15; + + +/** + * General exeption. Can give a stack trace and generates error messages + * from error numbers. + */ +class scd_exception : public std::exception +{ +public: + /** + * Constructor to create an exception with a string as cause. + * \param the cause + */ + scd_exception(const std::string& msg); + + /** + * Constroctor to create an exception with a string and an error + * message generated from an error number (errno) as cause. + * \param the cause prefix + * \param the error number to generate the error message for + */ + scd_exception(const std::string& msg, int errn); + + virtual ~scd_exception() throw() {} + + /** + * Returns a C-String showing the cause of the exception. + */ + virtual const char* what() throw(); + + /** + * Returns a C-String showing the stack trace. For name resolution + * -rdynamic has to be used while compiling. + */ + const char* stacktrace(); + +private: + std::string _msg; + std::string _backtrace; + + void _get_backtrace(); + +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_in_connector.cpp b/dol/src/dol/visitor/hdsd/scd/scd_in_connector.cpp new file mode 100644 index 0000000..e289d25 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_in_connector.cpp @@ -0,0 +1,130 @@ +#include "scd_in_connector.h" + +#include "scd_logging.h" +#include "scd_cont_man_master.h" + + +scd_in_connector::scd_in_connector(scd_simulator &sim, scd_socket* sock): + _sim(sim), _socket(sock), _is_connecting(true) +{ + // register this event handler + _sim.get_poller().register_handler(*this, SOCK_EV_READ | SOCK_EV_CLOSE); + + // set the socket for the reader + _reader.set_socket(*sock); +} + + +scd_in_connector::~scd_in_connector() +{ + if (_is_connecting) + { + _cleanup(); + } +} + + +bool scd_in_connector::is_connecting() { return _is_connecting; } + + +void scd_in_connector::handle_sock_ev(sock_ev events) +{ + if (events & SOCK_EV_CLOSE) + { + scd_debug("in_connector: received close event"); + _cleanup(); + } + else if (events & SOCK_EV_READ) + { + _reader.read(); + + if (!_socket->is_valid()) + { + // connection closed + _cleanup(); + scd_debug("in_connector: connection closed"); + return; + } + + if (!_reader.is_reading() && !_reader.has_command()) + { + // received an illegal command + scd_debug("in_connector: received illegal command"); + _cleanup(); + return; + } + + if (_reader.has_command()) + { + scd_command* cmd = _reader.get_command(); + + if (cmd->get_type() == SCD_CM_REGISTER && + cmd->get_subtype() == SCD_CM_NETSIM) + { + // register host + scd_debug("in_connector: received cmd \"register host\""); + + try + { + // check if we are the master + scd_cont_man_master& master = + dynamic_cast(_sim.get_cont_man()); + + _sim.get_poller().remove_handler(*this); + + master.connect_slave(*cmd, _socket); + + delete cmd; + _is_connecting = false; + return; + } + catch ( std::bad_cast e) + { + scd_warn("slave tried to register"); + delete cmd; + _cleanup(); + return; + } + } // end register host + else if (cmd->get_type() == SCD_CM_REGISTER && + cmd->get_subtype() == SCD_CM_CHANNEL) + { + // register channel + scd_debug("in_connector: received cmd \"register channel\""); + + _sim.get_poller().remove_handler(*this); + + _sim.get_chan_man().connect_channel(*cmd, _socket); + + delete cmd; + _is_connecting = false; + return; + } // end register channel + else + { + // received wrong command + scd_debug("in_connector: received unknown command"); + delete cmd; + _cleanup(); + return; + } + } // end receive command + } // end read event + +} // handle_sock_ev() + + +const scd_socket& scd_in_connector::get_sock() +{ + return *_socket; +} + + +void scd_in_connector::_cleanup() +{ + if (_is_connecting) + _sim.get_poller().remove_handler(*this); + _socket->close(); + delete _socket; + _is_connecting = false; +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_in_connector.h b/dol/src/dol/visitor/hdsd/scd/scd_in_connector.h new file mode 100644 index 0000000..46e3c10 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_in_connector.h @@ -0,0 +1,46 @@ +#ifndef SCD_IN_CONNECTOR_H +#define SCD_IN_CONNECTOR_H + +#include "scd_socket.h" +#include "scd_sock_poller.h" +#include "scd_simulator.h" +#include "scd_command_reader.h" + +/** + * Reads a command from a socket handled over by scd_init_listener + * and dispatches the connection to either the control or channel manager. + * This is used during the initialization phase. + */ +class scd_in_connector : public scd_sock_ev_handler_if +{ +public: + /** + * Constructor. + * \param sim the simulator + * \param sock the socket of the connection to read the command from + */ + scd_in_connector(scd_simulator &sim, scd_socket* sock); + + ~scd_in_connector(); + + /** + * Indicates if this in_connector is still reading the incomming command. + * It can be deconstructed when this indicates false. + * \return true if this in_connector is still working + */ + bool is_connecting(); + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket& get_sock(); + +private: + scd_socket* _socket; + scd_simulator &_sim; + bool _is_connecting; + scd_command_reader _reader; + + void _cleanup(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_init_listener.cpp b/dol/src/dol/visitor/hdsd/scd/scd_init_listener.cpp new file mode 100644 index 0000000..700df8b --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_init_listener.cpp @@ -0,0 +1,126 @@ +#include "scd_init_listener.h" + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_in_connector.h" + + +scd_init_listener::scd_init_listener(scd_simulator &sim, uint16_t port): + _sim(sim), _host(""), _port(port), _handler(false) {} + + +scd_init_listener::scd_init_listener(scd_simulator &sim, + const std::string &host, uint16_t port): + _sim(sim), _host(host), _port(port), _handler(false) {} + + +scd_init_listener::~scd_init_listener() +{ + cleanup(true); + close(); +} + + +void scd_init_listener::listen() +{ + if (!_socket.is_valid()) + { + // create listening socket + if (!_socket.create()) + throw scd_exception("creating listening socket failed"); + if (!_socket.bind(_host, _port)) + throw scd_exception("binding listening socket failed"); + if (!_socket.listen()) + throw scd_exception("listening to socket failed"); + + // register socket + if (!_handler) + { + _sim.get_poller().register_handler(*this, + SOCK_EV_READ | SOCK_EV_CLOSE); + _handler = true; + } + + scd_debug("socket listening"); + } + else + scd_warn("socket is already listening"); + +} // listen() + + +void scd_init_listener::close() +{ + if (_handler) + { + _sim.get_poller().remove_handler(*this); + _handler = false; + } + + if (_socket.is_valid()) + { + _socket.close(); + scd_debug("listener socket closed"); + } + +} // close() + + +void scd_init_listener::cleanup(bool hard) +{ + std::list::iterator iter; + + for( iter=_connectors.begin(); iter!=_connectors.end();) + { + if ( hard || !(*iter)->is_connecting() ) + { + delete *iter; + iter = _connectors.erase(iter); + } + else + iter++; + } +} // cleanup() + + +void scd_init_listener::handle_sock_ev(sock_ev events) +{ + if (events & SOCK_EV_READ) + { + scd_debug("incomming connection"); + + scd_socket* newconn = new scd_socket(); + + if (_socket.accept(*newconn)) + { + /* create a new connector that handles the connection + * and store it in the list of connectors */ + scd_in_connector* connector; + connector = new scd_in_connector(_sim, newconn); + _connectors.push_back(connector); + } + else + { + // connection could not be accepted + scd_debug("accepting connection failed"); + delete newconn; + } + } + + if (events & SOCK_EV_CLOSE) + { + throw scd_exception("init_listener: experienced an error"); + } + + if (!_socket.is_valid()) + { + throw scd_exception("init_listener: unexpectedly closed"); + } + +} // sock_ev_handler() + + +const scd_socket& scd_init_listener::get_sock() +{ + return _socket; +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_init_listener.h b/dol/src/dol/visitor/hdsd/scd/scd_init_listener.h new file mode 100644 index 0000000..5ae7b5a --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_init_listener.h @@ -0,0 +1,73 @@ +#ifndef SCD_INIT_LISTENER_H +#define SCD_INIT_LISTENER_H + +#include + +#include "scd_sock_poller.h" +#include "scd_socket.h" +#include "scd_simulator.h" +#include "scd_in_connector.h" + +/** + * Accepts incomming connection during initialization and instanciates + * a connector that handles the new connection. + */ +class scd_init_listener : public scd_sock_ev_handler_if +{ +public: + /** + * Constructor. Binds the listener to the specified TCP port on all + * available network interfaces. + * \param sim the simulator + * \param port TCP port to bind to + */ + scd_init_listener(scd_simulator &sim, uint16_t port); + + /** + * Constructor. Binds the listener to the specified TCP port + * only on the specified network interface. + * \param sim the simulator + * \param host IP or domain name of the interface to bind to + * \param port TCP port to bind to + */ + scd_init_listener(scd_simulator &sim, + const std::string &host, uint16_t port); + + /** + * Deconstructor. + */ + virtual ~scd_init_listener(); + + /** + * Creates the listening socket. + * \exception scd_exception if unexpected errors occure + */ + void listen(); + + /** + * Closes the listening socket. + */ + void close(); + + /** + * Removes connectors that finished their job. If the optional argument + * hard is true all connectors are destroyed independend if they + * have finished or not. + * \param hard if true all connectors will be destroyed + */ + void cleanup(bool hard = false); + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket& get_sock(); + +private: + scd_simulator &_sim; + std::string _host; + uint16_t _port; + scd_socket _socket; + std::list_connectors; + bool _handler; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_logging.cpp b/dol/src/dol/visitor/hdsd/scd/scd_logging.cpp new file mode 100644 index 0000000..5b3fc4c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_logging.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "scd_logging.h" + +static scd_loglevel _curr_level = SCD_INFO; + +void scd_log(const scd_loglevel &level, const string &str) +{ + std::ostream &out = std::clog; + + if (_curr_level > level) + return; + + switch(level) + { + case SCD_DEBUG: + out << "debug: "; + break; + case SCD_INFO: + out << "info: "; + break; + case SCD_WARN: + out << "warn: "; + break; + case SCD_ERROR: + out << "error: "; + break; + default: + out << "unknown: "; + break; + } + + out << str << std::endl; +} + +void scd_set_loglevel(const scd_loglevel &level) +{ + _curr_level = level; +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_logging.h b/dol/src/dol/visitor/hdsd/scd/scd_logging.h new file mode 100644 index 0000000..9112dc8 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_logging.h @@ -0,0 +1,56 @@ +#ifndef SCD_LOGGING_H +#define SCD_LOGGING_H + +#include +using std::string; + +/* Loglevels. Default is SCD_INFO */ +typedef int scd_loglevel; +const scd_loglevel SCD_DEBUG = 0; +const scd_loglevel SCD_INFO = 1; +const scd_loglevel SCD_WARN = 2; +const scd_loglevel SCD_ERROR = 3; + +/** + * Log message if the current loglevel is low enough. + */ +void scd_log(const scd_loglevel &level, const string &str); + +/** + * Log message with loglevel SCD_DEBUG. + */ +inline void scd_debug(const string &str) +{ + scd_log(SCD_DEBUG, str); +} + +/** + * Log message with loglevel SCD_INFO. + */ +inline void scd_info(const string &str) +{ + scd_log(SCD_INFO, str); +} + +/** + * Log message with loglevel SCD_WARN. + */ +inline void scd_warn(const string &str) +{ + scd_log(SCD_WARN, str); +} + +/** + * Log message with loglevel SCD_ERROR. + */ +inline void scd_error(const string &str) +{ + scd_log(SCD_ERROR, str); +} + +/** + * Set the current loglevel. Events with lower priorities are not logged. + */ +void scd_set_loglevel(const scd_loglevel &level); + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_out_connector.cpp b/dol/src/dol/visitor/hdsd/scd/scd_out_connector.cpp new file mode 100644 index 0000000..999a423 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_out_connector.cpp @@ -0,0 +1,185 @@ +#include "scd_out_connector.h" + +#include + +#include "scd_logging.h" +#include "scd_exception.h" +#include "scd_command.h" + + +scd_out_connector::scd_out_connector(scd_simulator& sim, uint16_t type, + const std::string& name): + _sim(sim), _name(name), + _is_connecting(true), _is_connected(false), _has_connection(false) +{ + // create socket + _socket = new scd_socket; + _socket->create(); + + // prepare register command (do not send yet) + scd_command* cmd = new scd_command(SCD_CM_REGISTER, type, name); + _writer = new scd_command_writer(); + _writer->set_socket(*_socket); + _writer->queue_command(cmd); +} + + +scd_out_connector::~scd_out_connector() +{ + close(); + delete _writer; +} + + +void scd_out_connector::connect_to(const std::string& host, uint16_t port) +{ + _host = host; + _port = port; + + // register the handler to check if a connection attempt succeeded + _sim.get_poller().register_handler(*this, SOCK_EV_WRITE); + + // start connection attempt + _conn_attempt(); + +} // connect_to() + + +bool scd_out_connector::is_connecting() { return _is_connecting; } + + +bool scd_out_connector::has_connection() { return _has_connection; } + + +scd_socket* scd_out_connector::get_connection() +{ + if (_has_connection) + { + _has_connection = false; + return _socket; + } + else + return NULL; +} + + +void scd_out_connector::process() +{ + if (_is_connecting && !_is_connected) + { + struct timeval now; + gettimeofday(&now, NULL); + + // check if an attempt had timed out (i.e. because of "drop" FW policy) + if ( _socket->is_connecting() && + (now.tv_sec - _last_con.tv_sec >= SCD_CON_TIMEOUT) ) + { + // close and reopen the socket + scd_debug("connecting to " + _host + " timed out"); + _sim.get_poller().remove_handler(*this); + _socket->close(); + _socket->create(); + _sim.get_poller().register_handler(*this, 0); + } + + // check if it is time to try again + if ( !_socket->is_connecting() && + (now.tv_sec - _last_con.tv_sec >= SCD_CON_RETRY) ) + { + scd_debug("reconnecting to " + _host); + _conn_attempt(); + _sim.get_poller().set_ev(*this, SOCK_EV_WRITE); + } + } // still trying to tcp-connect + +} // process() + + +void scd_out_connector::close() +{ + if (_is_connecting) + { + _sim.get_poller().remove_handler(*this); + } + + if (_is_connecting || _has_connection) + { + _socket->close(); + delete _socket; + + _is_connecting = false; + _has_connection = false; + } + + _is_connected = false; + +} // close() + + +const std::string& scd_out_connector::get_name() const { return _name; } + + +void scd_out_connector::handle_sock_ev(sock_ev events) +{ + if (events & SOCK_EV_CLOSE) + { + // the close event is only polled if _is_connected is true + scd_error("outgoing connection closed"); + close(); + return; + } // end close event + else if (events & SOCK_EV_WRITE) + { + if (!_is_connected) + { + if (_socket->connected_event()) + { + // connection established + scd_debug("ougoing connection established to " + _host); + _is_connected = true; + _sim.get_poller().set_ev(*this, SOCK_EV_WRITE | SOCK_EV_CLOSE); + } + else + { + // connection attempt failed + _sim.get_poller().set_ev(*this, 0); + return; + } + } + + assert( _is_connected || !_socket->is_connecting()); + + _writer->write(); + if (!_writer->is_writing()) + { + // done sending the command + _is_connecting = false; + _has_connection = true; + _sim.get_poller().remove_handler(*this); + } + + if (!_socket->is_valid()) + { + close(); + } + } // end write event + +} // handle_sock_ev() + + +const scd_socket& scd_out_connector::get_sock() { return *_socket; } + + +void scd_out_connector::_conn_attempt() +{ + if (_socket->connect(_host, _port)) + { + gettimeofday(&_last_con, NULL); + } + else + { + scd_error("unable to connect to " + _host); + close(); + } + +} // _conn_attempt() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_out_connector.h b/dol/src/dol/visitor/hdsd/scd/scd_out_connector.h new file mode 100644 index 0000000..f8a9a9c --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_out_connector.h @@ -0,0 +1,110 @@ +#ifndef SCD_OUT_CONNECTOR_H +#define SCD_OUT_CONNECTOR_H + +#include +#include +#include + +#include "scd_socket.h" +#include "scd_sock_poller.h" +#include "scd_simulator.h" +#include "scd_command_writer.h" + + +const time_t SCD_CON_TIMEOUT = 2; +const time_t SCD_CON_RETRY = 1; + + +/* forward declaration */ +class scd_simulator; + + +/** + * Initiates an outgoing connection and sends the register command + * to the other side. If an attempt to connect failes it is + * retried after some time. To drive this, process() has to be called + * repeatedly. + */ +class scd_out_connector : public scd_sock_ev_handler_if +{ +public: + /** + * Constructor. + * \param sim the simulator + * \param type what should be connected (SCD_CM_NETSIM or SCD_CM_CHANNEL) + * \param name the name of the ressource to connect (i.e. channel name + * or slave name) + */ + scd_out_connector(scd_simulator& sim, uint16_t type, + const std::string& name); + + virtual ~scd_out_connector(); + + /** + * Sets the host to connect to and starts the first connection attempt. + * + * \param host the IP or hostname to connect to + * \param port the port to connect to + */ + void connect_to(const std::string& host, uint16_t port); + + /** + * Indicates if this out_connector is still trying to connect or + * sending the command. + * * \return true if this out_connector is still working + */ + bool is_connecting(); + + /** + * Indicates if the command has been sent successfully and the registered + * connection can be collected. + * \return true if a connection is registered + */ + bool has_connection(); + + /** + * Returns the established and registered connection. + */ + scd_socket* get_connection(); + + + /** + * Checks if a time outs have occured. A connection attempt that is running + * for more than SCD_CON_TIMEOUT seconds is terminated and failed + * connection attempts are retried after SCD_CON_RETRY seconds. + */ + void process(); + + /** + * Stops to try to connect or terminates an already established + * connection. + */ + void close(); + + /** + * Returns the name of the ressource to connect. + */ + const std::string& get_name() const; + + /* scd_sock_ev_handler_if */ + void handle_sock_ev(sock_ev events); + const scd_socket& get_sock(); + +private: + scd_simulator &_sim; + std::string _name; + std::string _host; + uint16_t _port; + + bool _is_connecting; + bool _has_connection; + scd_socket* _socket; + scd_command_writer* _writer; + + bool _is_connected; + struct timeval _last_con; + + void _conn_attempt(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_rem_chan_if.h b/dol/src/dol/visitor/hdsd/scd/scd_rem_chan_if.h new file mode 100644 index 0000000..e478cc6 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_rem_chan_if.h @@ -0,0 +1,67 @@ +#ifndef SCD_REM_CHAN_IF_H +#define SCD_REM_CHAN_IF_H + +#include + + +/** + * Interface for SystemC channels with remote input. + */ +class scd_rem_chan_in_if +{ +public: + + /** + * Indicates how many bytes can be received by this channel. + */ + virtual size_t free() const = 0; + + /** + * Receive bytes, convert to internal data structures and generate + * notifications if necessary. + * + * \param buf pointer to buffer to read data from + * \param len number of bytes to receive (not larger than free()) + */ + virtual void receive(const void* buf, size_t len) = 0; + + /** + * Virtual deconstructor + */ + virtual ~scd_rem_chan_in_if() {} +}; + + +/** + * Interface for SystemC channels with remote output. + */ +class scd_rem_chan_out_if +{ +public: + + /** + * Inidcates how many bytes are available to be sent from this channel. + */ + virtual size_t available() const = 0; + + /** + * Get the available data to be sent. Does not guarantee that it is + * actually sent. At most available() bytes are sent. + */ + virtual const void* send() const = 0; + + /** + * Remove bytes from channel that have been successully sent. + * + * \param len number of bytes that have been sent (not larger than + * available()) + */ + virtual void remove(size_t len) = 0; + + /** + * Virtual deconstructor + */ + virtual ~scd_rem_chan_out_if() {} +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_in.cpp b/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_in.cpp new file mode 100644 index 0000000..62346fc --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_in.cpp @@ -0,0 +1,62 @@ +#include "scd_rem_fifo_in.h" + +#include + +#include "scd_logging.h" + + +scd_rem_fifo_in::scd_rem_fifo_in(sc_module_name name, int size): + sc_prim_channel(name), _num_elements(0), _first(0), _size(size) +{ + assert(size > 0); + _data = new char[size]; +} + + +scd_rem_fifo_in::~scd_rem_fifo_in() +{ + delete _data; +} + + +void scd_rem_fifo_in::read(char &c) +{ + while (_num_elements == 0) + wait(_write_event); + + c = _data[_first]; + _num_elements--; + _first = (_first + 1) % _size; +} + + +int scd_rem_fifo_in::rtest(int size) +{ + return (size <= _num_elements) ? 1 : 0; +} + + +void scd_rem_fifo_in::reset() { _num_elements = _first = 0; } + + +int scd_rem_fifo_in::num_available() { return _num_elements ;} + + +size_t scd_rem_fifo_in::free() const { return _size - _num_elements; } + + +void scd_rem_fifo_in::receive(const void* buf, size_t len) +{ + if (len == 0) + return; + + assert(_num_elements + len <= _size); + + for (int i=0; i + +#include "scd_logging.h" + + +scd_rem_fifo_out::scd_rem_fifo_out(sc_module_name name, int size): + sc_prim_channel(name), _num_elements(0), _first(0), _size(size) +{ + assert(size > 0); + _data = new char[size]; +} + + +scd_rem_fifo_out::~scd_rem_fifo_out() +{ + delete _data; +} + + +void scd_rem_fifo_out::write(char c) +{ + while (_num_elements == _size) + wait(_read_event); + + _data[ (_first + _num_elements) % _size ] = c; + _num_elements++; +} + + +int scd_rem_fifo_out::wtest(int size) +{ + return (size <= _size - _num_elements) ? 1 : 0; +} + + +void scd_rem_fifo_out::reset() { _num_elements = _first = 0; } + + +size_t scd_rem_fifo_out::available() const +{ + size_t avail; + + if (_num_elements <= _size - _first) + avail = _num_elements; + else + avail = _size - _first; + + return avail; +} + + +const void* scd_rem_fifo_out::send() const { return _data + _first; } + + +void scd_rem_fifo_out::remove(size_t len) +{ + if (len == 0) + return; + + assert(_num_elements >= len); + + _first = (_first + len) % _size; + + _num_elements -= len; + + _read_event.notify(); +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_out.h b/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_out.h new file mode 100644 index 0000000..2025a1e --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_rem_fifo_out.h @@ -0,0 +1,83 @@ +/***************************************************************************** + + The following code is derived, directly or inror: pointdirectly, from the SystemC + source code Copyright (c) 1996-2007 by all Contributors. + All Rights reserved. + + The contents of this file are subject to the restrictions and limitations + set forth in the SystemC Open Source License Version 2.4 (the "License"); + You may not use this file except in compliance with such restrictions and + limitations. You may obtain instructions on how to receive a copy of the + License at http://www.systemc.org/. Software distributed by Contributors + under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF + ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + *****************************************************************************/ + +/***************************************************************************** + + simple_fifo.cpp -- Simple SystemC 2.0 producer/consumer example. + + From "An Introduction to System Level Modeling in + SystemC 2.0". By Stuart Swan, Cadence Design Systems. + Available at www.systemc.org + + Original Author: Stuart Swan, Cadence Design Systems, 2001-06-18 + + *****************************************************************************/ + +/***************************************************************************** + + MODIFICATION LOG - modifiers, enter your name, affiliation, date and + changes you are making here. + + Name, Affiliation, Date: Fabian Hugelshofer, ETH Zurich, 13.11.2007 + Description of Modification: Used code as base for remote fifo channels. + + *****************************************************************************/ + +#ifndef SCD_REM_FIFO_OUT_H_ +#define SCD_REM_FIFO_OUT_H_ + +#include "systemc" + +#include "simple_fifo.h" // for write_if +#include "scd_rem_chan_if.h" + + +/** + * FIFO channel with remote output. Data written to this FIFO endpoint + * is transmitted to another host. + */ +class scd_rem_fifo_out : public sc_core::sc_prim_channel, public write_if, + public scd_rem_chan_out_if +{ +public: + /** + * Constructor. + * \param name the name of the channel (is passed to parent constructor) + * \param size size of the output buffer in bytes + */ + scd_rem_fifo_out(sc_module_name name, int size); + + virtual ~scd_rem_fifo_out(); + + /* write_if */ + void write(char c); + int wtest(int size); + void reset(); + + /* scd_rem_chan_out_if */ + size_t available() const; + const void* send() const; + void remove(size_t len); + +private: + int _size; + char* _data; + int _num_elements, _first; + sc_event _read_event; +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_simulator.cpp b/dol/src/dol/visitor/hdsd/scd/scd_simulator.cpp new file mode 100644 index 0000000..86c2196 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_simulator.cpp @@ -0,0 +1,218 @@ +#include "scd_simulator.h" + +#include +#include + +#include "systemc" + +#include "scd_cont_man_master.h" +#include "scd_cont_man_slave.h" +#include "scd_init_listener.h" +#include "scd_logging.h" + + +scd_simulator::scd_simulator(const std::string& name, + const std::string &loc_host, uint16_t loc_port, bool master): + _name(name), _loc_host(loc_host), _loc_port(loc_port) +{ + _poller = new scd_sock_poller; + + if (master == SCD_MASTER) + _cont_man = new scd_cont_man_master(*this); + else + _cont_man = new scd_cont_man_slave(*this); + + _chan_man = new scd_chan_man(*this); +} + + +scd_simulator::scd_simulator(const std::string& name, uint16_t loc_port, + bool master): + _name(name), _loc_host(""), _loc_port(loc_port) +{ + _poller = new scd_sock_poller; + + if (master == SCD_MASTER) + _cont_man = new scd_cont_man_master(*this); + else + _cont_man = new scd_cont_man_slave(*this); + + _chan_man = new scd_chan_man(*this); +} + + +scd_simulator::~scd_simulator() +{ + delete _chan_man; + delete _cont_man; + delete _poller; +} + + +const std::string& scd_simulator::get_name() const { return _name; } + + +scd_sock_poller& scd_simulator::get_poller() { return *_poller; } + + +scd_chan_man& scd_simulator::get_chan_man() { return *_chan_man; } + + +scd_cont_man& scd_simulator::get_cont_man() { return *_cont_man; } + + +bool scd_simulator::init() +{ + scd_info("initialization start"); + + /* open port for incomming connections */ + scd_init_listener listener(*this, _loc_host, _loc_port); + listener.listen(); + + /* + * init until the initialization failed or chan_man and cont_man are + * successfully initiated + */ + while ( !_cont_man->failed() && + ( !_chan_man->ready() || !_cont_man->active() ) ) + { + // poll the sockets and execute callback handlers + _poller->process(); + + // connect channels + _chan_man->init_process(); + + // process state transitions + _cont_man->process(); + + /* as we might be waiting for a peer coming up we can not use + * a blocking poll as errors would always exist and the poller would + * wake up immediately + * => sleep some milliseconds + */ + usleep(50); + } + + listener.close(); + + if (_cont_man->failed()) + { + scd_error("initialization failed"); + return false; + } + else + { + scd_info("initialization done"); + return true; + } + +} // init() + + +bool scd_simulator::start() +{ + scd_info("simulation start"); + + /* first delta cycle to initialize the SystemC scheduler + * else the _pending*() functions would fail */ + sc_core::sc_start(sc_core::SC_ZERO_TIME); + + while (_cont_man->active()) + { + /* signalize the simulation state to the controller */ + if (_pending()) + { + if (_pending_now()) + { + // events in this time step + _cont_man->set_busy(); + } + else + { + // signalize time step until next event + sc_core::sc_time step; + step = _next_time() - sc_core::sc_time_stamp(); + _cont_man->set_idle(step); + } + } + else + _cont_man->set_done(); + + // check for state changes + _cont_man->process(); + + /* run simulation if something to do and in right state */ + if (_cont_man->busy()) + { + // simulate one delta cycle + sc_core::sc_start(sc_core::SC_ZERO_TIME); + + // check if we have to wait for new socket events + _chan_man->process(); + + // poll the sockets to induce transmissions + _poller->process(); + } + else + { + if (_cont_man->advance_time()) + { + sc_core::sc_time step = _cont_man->get_time_step(); + // advance simulation time, nothing should actually be simulated + sc_start(step); + scd_info("time advanced by " + step.to_string() + " to " + + sc_core::sc_time_stamp().to_string() ); + } + else if ( _cont_man->active() ) + { + // wait for activity on sockets + _poller->wait_process(); + } + } + + } // while simulation is running + + /* simulation terminated */ + + if (_cont_man->failed()) + { + scd_error("simulation failed"); + return false; + } + else + { + scd_info("simulation done"); + return true; + } + +} // start() + + +/** + * Returns true if events exist at current simulation time. + */ +bool scd_simulator::_pending_now() +{ + return sc_core::sc_pending_activity_at_current_time(); +} + + +/** + * Returns true if events exist at current or later simulation time. + */ +bool scd_simulator::_pending() +{ + sc_core::sc_time next = _next_time(); + return ( (next != sc_core::SC_ZERO_TIME) || _pending_now() ); +} + + +/** + * Returns the time of the next earliest timed event + * or SC_ZERO_TIME if no such event exists. + */ +const sc_core::sc_time scd_simulator::_next_time() +{ + return sc_core::sc_get_curr_simcontext()->next_time(); +} + diff --git a/dol/src/dol/visitor/hdsd/scd/scd_simulator.h b/dol/src/dol/visitor/hdsd/scd/scd_simulator.h new file mode 100644 index 0000000..48b4b3f --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_simulator.h @@ -0,0 +1,118 @@ +#ifndef SCD_SIMULATOR_H +#define SCD_SIMULATOR_H + +#include +#include + +#include "scd_sock_poller.h" +#include "scd_chan_man.h" +#include "scd_cont_man.h" + + +/* forward declaration */ +class scd_chan_man; +class scd_cont_man_if; + + +/** + * Simulator to run a distributed SystemC simulation. First the + * channel with remote endpoints, the slave controllers and the + * master controller have to be registered with the channel manager + * and the control manager. Second the simulation has to be + * initialized and third the simulation can be started. + */ +class scd_simulator +{ +public: + + /** + * Constructor. Binds the simulator to the specified TCP port + * only on the specified network interface. + * \param name the name of this simulator + * \param loc_host IP or domain name of the interface to bind to + * \param loc_port TCP port to bind to + * \bool master SCD_MASTER if this simulator is the master, else + * SCD_SLAVE + */ + scd_simulator(const std::string& name, const std::string &loc_host, + uint16_t loc_port, bool master); + + /** + * Constructor. Binds the simulator to the specified TCP port on all + * available network interfaces. + * \param name the name of this simulator + * \param loc_port TCP port to bind to + * \bool master SCD_MASTER if this simulator is the master, else + * SCD_SLAVE + */ + scd_simulator(const std::string& name, uint16_t loc_port, bool master); + + virtual ~scd_simulator(); + + + /** + * Returns the name of this simulator. + */ + const std::string& get_name() const; + + /** + * Returns the socket poller of this simulation. + */ + scd_sock_poller& get_poller(); + + /** + * Returns the channel manager of this simulation. + */ + scd_chan_man& get_chan_man(); + + /** + * Returns the control manager of this simulation. + */ + scd_cont_man& get_cont_man(); + + /** + * Initializes the distributed simulation. Connects all remote channels + * and the control infrastructure. The channels, the slaves and the master + * have to be registered before initializating. A simulation can only + * be initialized once. + */ + bool init(); + + /** + * Runs the simulation until an error occures or no more events + * exist globally. A simulation can only be started once. + */ + bool start(); + + +private: + scd_sock_poller* _poller; + scd_chan_man* _chan_man; + scd_cont_man* _cont_man; + + std::string _name; + std::string _loc_host; + uint16_t _loc_port; + + /* member functions */ + + /** + * Indicates if events exist for the current simulation time. + * \return true if events exist for the current time + */ + bool _pending_now(); + + /** + * Indicates if events exist in the event queues. + * \return true if events exist + */ + bool _pending(); + + /** + * Returns the absolute time of the next event in the queues. + * \retunrs the absoulte time of the next event or 0 if no such event exists + */ + const sc_core::sc_time _next_time(); +}; + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.cpp b/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.cpp new file mode 100644 index 0000000..1cff374 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.cpp @@ -0,0 +1,201 @@ +#include "scd_sock_poller.h" + +#include +#include +#include +#include + +#include "scd_exception.h" +#include "scd_logging.h" + + +bool scd_sock_poller::register_handler(scd_sock_ev_handler_if& handler, + sock_ev events) +{ + // can not register the same handler twice + if (_handler_exists(handler)) + { + scd_warn("socket handler already registered while registering"); + return false; + } + + // register handler + _handlers.push_back(&handler); + + // register socket and events + struct pollfd fd; + fd.fd = handler.get_sock()._socket; + fd.events = events; + _events.push_back(fd); + return true; + +} // register_handler() + + +bool scd_sock_poller::remove_handler(const scd_sock_ev_handler_if &handler) +{ + int idx = _get_index(handler); + + // check if handler was found + if (idx == -1) + { + scd_warn("socket handler not registered while removing poll handler"); + return false; + } + + // remove events and handler from vectors + _events.erase(_events.begin() + idx); + _handlers.erase(_handlers.begin() + idx); + + return true; + +} // remove_handler() + + +bool scd_sock_poller::set_ev(const scd_sock_ev_handler_if &handler, + sock_ev events) +{ + int idx = _get_index(handler); + + if (idx == -1) + { + scd_warn("socket handler not registered while setting poll events"); + return false; + } + + _events[idx].events = events; + + return true; +} // set_ev() + + +bool scd_sock_poller::get_ev(const scd_sock_ev_handler_if &handler, + sock_ev &events) const +{ + int idx = _get_index(handler); + + if (idx == -1) + { + scd_warn("socket handler not registered while getting poll events"); + return false; + } + + events = _events[idx].events; + + return true; + +} // get_ev() + + +bool scd_sock_poller::wait(int ms) +{ + int ret = _poll(ms); + if (ret > 0) + return true; + else + return false; +} + + +bool scd_sock_poller::process() +{ + int ret = _poll(0); + + if (ret != 0) // events occured + { + _callback(); + return true; + } + else + return false; + +} // process() + + +void scd_sock_poller::wait_process() +{ + _poll(-1); + _callback(); + +} // wait_process() + + +bool scd_sock_poller::_handler_exists(const scd_sock_ev_handler_if &handler) + const +{ + std::vector::const_iterator iter; + + iter = std::find(_handlers.begin(), _handlers.end(), &handler); + + if (iter == _handlers.end()) + return false; + else + return true; + +} // _handler_exists() + + +int scd_sock_poller::_get_index(const scd_sock_ev_handler_if &handler) const +{ + std::vector::const_iterator iter; + + iter = std::find(_handlers.begin(), _handlers.end(), &handler); + + if (iter == _handlers.end()) + { + // handler not found + return -1; + } + else + { + // return index (random access iterator) + return iter - _handlers.begin(); + } + +} // _get_index() + + +int scd_sock_poller::_poll(int ms) +{ + int ret; + + if (ms < 0 && _events.size() == 0) + { + scd_debug("immediately returning form infinite poll: no events to watch"); + return 0; + } + + do + { + ret = poll(&_events[0], _events.size(), ms); + if (ret == -1 && errno != EINTR) + { + std::string error("poll(): "); + error += std::string(strerror(errno)); + throw scd_exception(error); + } + } + while (ret == -1 && errno == EINTR); + + return ret; + +} // _poll() + + +void scd_sock_poller::_callback() +{ + /* called handlers might remove themselves. as the order + * should not break we operate on copies here */ + std::vector events_cpy = _events; + std::vector handlers_cpy = _handlers; + + // for all handlers + for (int i=0; i callback handler + handlers_cpy[i]->handle_sock_ev(events_cpy[i].revents); + } + } // for all events +} // _callback() diff --git a/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.h b/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.h new file mode 100644 index 0000000..b19cece --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_sock_poller.h @@ -0,0 +1,144 @@ +#ifndef SCD_SOCK_POLLER_H +#define SCD_SOCK_POLLER_H + +#include +#include "poll.h" + +#include "scd_socket.h" + + +typedef short sock_ev; +const sock_ev SOCK_EV_READ = POLLIN; +const sock_ev SOCK_EV_WRITE = POLLOUT; +const sock_ev SOCK_EV_CLOSE = POLLERR | POLLHUP | POLLNVAL; + + +/** + * Interface for classes able to handle socket events. + */ +class scd_sock_ev_handler_if +{ +public: + /** + * Executes the handler for socket events that have been registered + * and have ocured. Callback carried out by scd_sock_poller. + *\param events the events that occured + */ + virtual void handle_sock_ev(sock_ev events) = 0; + + /** + * Returns the socket that should be watched. The socket must + * be the same as long the handler is registered. + */ + virtual const scd_socket &get_sock() = 0; + + virtual ~scd_sock_ev_handler_if() {}; +}; + + +/** + * Dispatcher that watches sockets and executes call backs if watched + * event occure. Socket events (sock_ev) are an OR combination of flags + * supported by ::poll. See "man 2 poll". The watched events for a specific + * event handler stay watched until the events are overwritten or the handler + * is removed. + */ +class scd_sock_poller +{ +public: + /** + * Register a socket event handler (a class handling socket events). + * Optionally the events to watch can be specified. + * \param handler the object that wants to watch a socket + * \param events (optional) the events to watch + * \return true if the handler could be registered succesfully + */ + bool register_handler(scd_sock_ev_handler_if &handler, sock_ev events = 0); + + /** + * Remove a socket event handler. The socket is not watched anymore. + * \param the object that has previously been registered + * \return false if no such handler was registered + */ + bool remove_handler(const scd_sock_ev_handler_if &handler); + + /** + * Set events to watch. The events expire only when overwritten + * by calling this function again or by removing the handler. + * \param handler the handler that has been registered before + * \param events the events the handler is interested in + * \return false if the handler has not been registered before + */ + bool set_ev(const scd_sock_ev_handler_if &handler, sock_ev events); + + /** + * Gets the events that are currently watched for the handler. + * \param the handler to get the watched events for + * \param events the variable where the watched events are written to + * \return false if the handler has not been registered before + */ + bool get_ev(const scd_sock_ev_handler_if &handler, sock_ev &events) const; + + /** + * Wait (blocking) for registered events or a timeout to occure. + * No callbacks are executed. + * \param milis timeout in milliseconds to wait for events, if omitted + * or -1 it is waited until events occure + * \return true if events occured, false otherwise + * \exception scd_exception if unexpected errors occured + */ + bool wait(int ms = -1); + + /** + * Check if events occured and callback the affected handlers. + * \return true if some events occured + * \exception scd_exception if unexcpected errors occured + */ + bool process(); + + /** + * Wait (blocking) for registered events to occure and callback the + * affected handlers. + * \exception scd_exception if unexcpected errors occured + */ + void wait_process(); + +private: + /* member variables */ + std::vector _handlers; + std::vector _events; + + /* member functions */ + + /** + * Checks if a handler is already registered. + * \param handler the handler to check for existence + * \return true if the handler has been registered before + */ + bool _handler_exists(const scd_sock_ev_handler_if& handler) const; + + /** + * Returns the index of an event handler in the vector. This is the same + * index as the corresponding pollfd. + * \param handler the handler to get the index of + * \return the index of the handler or -1 if it could not be found + */ + int _get_index(const scd_sock_ev_handler_if& handler) const; + + /** + * Polls during a specified amount of time. + * \param ms miliseconds to block (-1 for infinite) + * \exception scd_exception if an unexpected error occured + * \return number of sockets for witch events occured + */ + int _poll(int ms); + + /** + * Calls handlers back for which events occured. + */ + void _callback(); +}; + +//scd_sock_poller* scd_get_sock_poller(); + +#endif diff --git a/dol/src/dol/visitor/hdsd/scd/scd_socket.cpp b/dol/src/dol/visitor/hdsd/scd/scd_socket.cpp new file mode 100644 index 0000000..a5ca5de --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_socket.cpp @@ -0,0 +1,439 @@ +#include "scd_socket.h" + +#include +#include // for TCP_NODELAY +#include +#include +#include + +#include "scd_logging.h" +#include "scd_exception.h" + +scd_socket::scd_socket() +{ + _socket = -1; + _is_connecting = false; + _is_connected = false; +} + +scd_socket::~scd_socket() {} + +bool scd_socket::is_valid() const +{ + return (_socket != -1); +} + +bool scd_socket::create() +{ + // check if socket already created before + if ( is_valid() ) + { + return false; + } + + // create TCP socket + _socket = socket(PF_INET, SOCK_STREAM, 0); + + assert(is_valid()); + + _set_blocking(false); + + _is_connecting = false; + _is_connected = false; + + return true; + +} // create() + +void scd_socket::close() +{ + if ( is_valid() ) + { + ::close(_socket); + } + + _socket = -1; + _is_connecting = false; + _is_connected = false; +} + +bool scd_socket::bind(const std::string &loc_name, const uint16_t loc_port) +{ + int ret; + + if ( ! is_valid() ) + { + return false; + } + + // prepare the local address + sockaddr_in loc_addr; + if ( !_get_sockaddr(loc_addr, loc_name, loc_port) ) + { + return false; + } + + // set socket reusable (to recreate listener without timeout) + int on = 1; + ret = setsockopt( _socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ); + if (ret == -1) + { + throw scd_exception("setsockopt()", errno); + } + + ret = ::bind( _socket, reinterpret_cast(&loc_addr), + sizeof( loc_addr ) ); + + if ( ret == -1 ) + { + switch(errno) + { + case EACCES: + scd_error("must be root to bind to desired port"); + break; + case EADDRINUSE: + scd_error("unable to bind as the address is already in use"); + break; + default: + throw scd_exception("bind()", errno); + } + return false; + } + + return true; + +} // bind() + + +bool scd_socket::bind(const uint16_t loc_port) +{ + return bind("", loc_port); +} // bind() + + +bool scd_socket::listen() +{ + int ret; + + if ( !is_valid() ) + { + return false; + } + + ret = ::listen(_socket, SCD_MAXCONN); + + if (ret == -1) + { + throw scd_exception("listen()", errno); + } + + return true; +} // listen() + +bool scd_socket::accept(scd_socket &new_sock) +{ + int ret; + + if ( !is_valid() ) + { + return false; + } + + ret = ::accept(_socket, NULL, NULL); + + if (ret < 0) + { + switch (errno) + { + case EAGAIN: + case ECONNABORTED: + case EINTR: + case EPROTO: + return false; + break; + default: + throw scd_exception("accept()", errno); + break; + } + } + else + { + new_sock._is_connected = true; + new_sock._socket = ret; + new_sock._set_blocking(false); + #ifdef TCP_NODELAY + new_sock._set_nodelay(); + #endif + } + + return true; + +} // accept() + + +bool scd_socket::connect(const std::string &rem_name, const uint16_t rem_port) +{ + int ret; + + if ( !is_valid() || _is_connecting || _is_connected ) + return false; + + sockaddr_in rem_addr; + if ( !_get_sockaddr(rem_addr, rem_name, rem_port) ) + return false; + + ret = ::connect(_socket, reinterpret_cast(&rem_addr), + sizeof(rem_addr)); + + if (ret == -1) + { + switch(errno) + { + case EINPROGRESS: + _is_connecting = true; + break; + case ECONNABORTED: + case ECONNREFUSED: + case EINTR: + case ENETUNREACH: + case ETIMEDOUT: + break; + case EALREADY: // should be prevented by _is_connecting + default: + throw scd_exception("connect()", errno); + break; + } + } + else // ret == 0 + { + _is_connected = true; + _is_connecting = false; + } + + return true; + +} // connect() + +bool scd_socket::is_connecting() +{ + if ( !is_valid() || !_is_connecting || _is_connected ) + return false; + + return true; + +} // is_connecting() + + +bool scd_socket::is_connected() +{ + if ( !is_valid() || _is_connecting || !_is_connected) + return false; + + return true; + +} // is_connecting() + + +bool scd_socket::connected_event() +{ + if (_is_connected) + return true; + + if ( !is_valid() ) + { + scd_warn("write event occured on an invalid socket"); + return false; + } + else if ( !_is_connecting ) + { + scd_warn("checking connected event while not connecting"); + return false; + } + + // get possible errors from connection attempt + int err, ret; + socklen_t err_size = sizeof(err); + ret = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &err, &err_size); + + if (ret == -1) + { + throw scd_exception("getsockopt()", errno); + } + + // handle errors + switch(err) + { + case 0: + // connection established + _is_connecting = false; + _is_connected = true; + #ifdef TCP_NODELAY + _set_nodelay(); + #endif + return true; + break; + case ECONNABORTED: + case ECONNREFUSED: + case EINTR: + case ENETUNREACH: + case ETIMEDOUT: + // connection attempt failed + _is_connecting = false; + return false; + break; + case EINPROGRESS: + // should not be possible as we received a completion event + throw scd_exception("checking connected event while still connecting"); + break; + case EALREADY: // should be prevented by _is_connecting + default: + throw scd_exception("connect()", errno); + break; + } + + // not reached +} //connected_event() + + +size_t scd_socket::send(const void* buf, size_t len) +{ + ssize_t ret; + + if ( !is_valid() || len == 0 || !_is_connected ) + return 0; + + ret = ::send(_socket, buf, len, MSG_NOSIGNAL); + + if (ret < 0) + { + switch(errno) + { + case ECONNRESET: + case EPIPE: + case ETIMEDOUT: + close(); + break; + case EWOULDBLOCK: + break; + default: + throw scd_exception("send()", errno); + break; + } + return 0; + } + else + return ret; + +} // send() + +size_t scd_socket::recv(void* buf, size_t len) +{ + ssize_t ret; + + if ( !is_valid() || len == 0 || !_is_connected) + return 0; + + ret = ::recv(_socket, buf, len, MSG_NOSIGNAL); + + if (ret < 0) + { + switch(errno) + { + case ETIMEDOUT: + close(); + break; + case EAGAIN: + case EINTR: + break; + case ENOTCONN: + default: + throw scd_exception("recv()", errno); + break; + } + return 0; + } + else if (ret == 0) + { + close(); + return 0; + } + else + return ret; + +} // recv() + +bool scd_socket::_get_sockaddr(sockaddr_in &addr, const std::string &name, + const uint16_t port) const +{ + // flush addr + memset( &addr, 0, sizeof(addr) ); + + /* set content */ + addr.sin_family = AF_INET; + addr.sin_port = htons( port ); + if ( name.empty() ) + { + // bind to all local addresses + addr.sin_addr.s_addr = INADDR_ANY; + } + else + { + // bint to specified address only + struct hostent* he; + + // resolve name + he = gethostbyname2(&name[0], AF_INET); + if (he == NULL) + return false; + + // copy address (network order) + addr.sin_addr.s_addr = + reinterpret_cast(he->h_addr_list[0])->s_addr; + } + + return true; + +} // _get_sockaddr() + + +bool scd_socket::_set_blocking(const bool mode) +{ + if ( !is_valid() ) + { + return false; + } + + int opts = fcntl( _socket, F_GETFL ); + + if ( opts < 0 ) + { + throw scd_exception("fcntl()", errno); + } + + if ( !mode ) + opts = ( opts | O_NONBLOCK ); + else + opts = ( opts & ~O_NONBLOCK ); + + fcntl( _socket, F_SETFL,opts ); + + return true; + +} //_set_blocking() + + +bool scd_socket::_set_nodelay() +{ + if ( !is_valid()) + return false; + + // disable Nagle algorithm + int on = 1; + int ret = setsockopt( _socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) ); + if (ret == -1) + { + throw scd_exception("setsockopt()", errno); + } + + return true; +} diff --git a/dol/src/dol/visitor/hdsd/scd/scd_socket.h b/dol/src/dol/visitor/hdsd/scd/scd_socket.h new file mode 100644 index 0000000..30c2e79 --- /dev/null +++ b/dol/src/dol/visitor/hdsd/scd/scd_socket.h @@ -0,0 +1,168 @@ +#ifndef SCD_SOCKET_H +#define SCD_SOCKET_H + +#include +#include +#include + +const int SCD_MAXCONN = 100; + +// forward declaration for friends +class scd_sock_poller; + +/** + * Berkeley TCP socket API wrapper class. The socket is put into non blocking + * mode. + */ +class scd_socket +{ + friend class scd_sock_poller; + +public: + scd_socket(); + virtual ~scd_socket(); + + /** + * Returns true if this socket has been successfully created and + * has not been closed since then. Use this function to check if an + * error occured. + */ + bool is_valid() const; + + /** + * Creates the socket. + * \return false if the socket is already valid + * \exception scd_exception if unexpected errors occured + */ + bool create(); + + /** + * Closes the socket and marks it as invalid. + */ + void close(); + + /** + * Binds the socket to a local address and port. + * \param loc_name the local IP or hostname or empty string + * \param loc_port the local port or 0 + * \return false if the socket is not valid or an invalid address + * was specified + * \exception scd_exception if ::bind() failed for unexpected reason + */ + bool bind(const std::string &loc_name, const uint16_t loc_port); + + /** + * Binds the socket to a port. + * \param loc_port the local port + * \exception scd_exception if ::bind() failed for unexpected reason + * \return false if the sockat is not valid + */ + bool bind(const uint16_t loc_port); + + /** + * Marks a previously bound socket as listening. + * \return false if the socket is not valid + * \exception scd_exception if ::listen() failed + */ + bool listen(); + + /** + * Accept a new connection from a listening socket. + * \param new_sock the object to hold the new connection + * \return true if a new connection was accepted + * \exception scd_exception if unexpected errors occured + */ + bool accept(scd_socket &new_sock); + + /** + * Initiates a connection attempt to a remote host. Use is_connecting() + * to check whether this attempt is still in progress or is_connected() + * to check whether the socket is connected. If the connection attempt + * is in progress, the socket has to be polled for a write event. + * If this event occures its handler shall call connected_event() + * to check if the connection was established successully. + * It is not possible to try to connect again if the write event + * is not polled and processed by calling connected_event() after + * is_connecting() has indicated that the attempt is still in progress. + * \param rem_name the hostname of the remote host (IP or host name) + * \param rem_port the port to connect to + * \return true if the connection attempt was started successfully, + * false if the hostname could not be resolved or a connection attempt + * was not possible + * \exception scd_exception if unexpected errors occured + */ + bool connect(const std::string &rem_name, const uint16_t rem_port); + + /** + * Indicates if a previous connection attempt is still in progress. + * \return true if a conneciton attempt is still in progress + */ + bool is_connecting(); + + /** + * Indicates if this socket is connected to a peer either while it has + * been accepted from an incoming connection or the outgoing connection + * is established. + * \return true if the socket is valid and connected + */ + bool is_connected(); + + /** + * Check if a connection attempt has succeeded on a write event. Shall + * only be called from the write event handler. Especially after a + * connection attempt was in progress. Can also be called if the + * connection has already been established. + * \return true if the socket is valid and the connection is established + * \exception scd_exception if unexpected errors occured + */ + bool connected_event(); + + /** + * Tries to send at most len data from the buffer. + * Might close the socket on errors. + * \return the number of successfully sent bytes + * \exception scd_exception if unexpected errors occured + */ + size_t send(const void* buf, size_t len); + + /** + * Tries to receive at most len data to the buffer. + * Might close the socket on errors. + * \return the number of successfully received bytes + * \exception scd_exception if unexpected errors occured + */ + size_t recv(void* buf, size_t len); + + +private: + /* member variables */ + + int _socket; + bool _is_connecting; + bool _is_connected; + + /* member functions */ + + /** + * Initializes a sockaddr_in struct. Can resolve hostnames. + */ + bool _get_sockaddr(sockaddr_in &addr, const std::string &name, + const uint16_t port) const; + + /** + * Sets the socket in blocking or non blocking mode. + * \param mode true to set blocking mode + * \return false if the socket is not valid + * \exception scd_exception if unexpected errors occured + */ + bool _set_blocking(const bool mode); + + /** + * Disables the Nagle algorithm which buffers outgoing data up to 200ms + * before sending it. Disabling causes more network traffic but + * decreases the delay. + */ + bool _set_nodelay(); +}; + +#endif diff --git a/dol/src/dol/visitor/package.html b/dol/src/dol/visitor/package.html new file mode 100644 index 0000000..94a87f3 --- /dev/null +++ b/dol/src/dol/visitor/package.html @@ -0,0 +1,20 @@ + + + + + + +Code generator. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/protothread/ProtothreadMakefileVisitor.java b/dol/src/dol/visitor/protothread/ProtothreadMakefileVisitor.java new file mode 100644 index 0000000..184a928 --- /dev/null +++ b/dol/src/dol/visitor/protothread/ProtothreadMakefileVisitor.java @@ -0,0 +1,63 @@ +/* $Id: ProtothreadMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.protothread; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * + */ +public class ProtothreadMakefileVisitor extends PNVisitor { + + /** + * + */ + public ProtothreadMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("CXX = g++"); + ps.println("CXXFLAGS = -g -Wall"); + ps.println("COMPILE = ${CXX} ${CXXFLAGS} -c"); + ps.println("LINK = ${CXX}"); + ps.println("LIB_INC = -Ilib -Iwrappers"); + ps.println(); + ps.println("src := $(wildcard lib/*.cpp) " + + "$(wildcard wrappers/*.cpp) $(wildcard *.cpp)"); + ps.println("obj = $(src:.cpp=.o)"); + ps.println(); + ps.println("app : ${obj} ${src}"); + ps.println("\t${LINK} -o " + _name + " $(obj)"); + ps.println(); + ps.println("%.o :"); + ps.println("\t${COMPILE} -o $(*D)/$(*F).o $(*D)/$(*F).cpp " + + "$(LIB_INC)"); + ps.println(); + ps.println("clean :"); + ps.println("\trm ${obj}"); + } + catch (IOException e) { + System.out.println(" Protothread Makefile Visitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected String _name = "sc_application"; +} + diff --git a/dol/src/dol/visitor/protothread/ProtothreadModuleVisitor.java b/dol/src/dol/visitor/protothread/ProtothreadModuleVisitor.java new file mode 100644 index 0000000..897d496 --- /dev/null +++ b/dol/src/dol/visitor/protothread/ProtothreadModuleVisitor.java @@ -0,0 +1,170 @@ +/* $Id: ProtothreadModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.protothread; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.StringTokenizer; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + */ +public class ProtothreadModuleVisitor extends PNVisitor { + + /** + * Constructor. + */ + public ProtothreadModuleVisitor(String dir) { + _dir = dir; + } + + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "sc_application.cpp"; + OutputStream file = new FileOutputStream(filename); + _code = new CodePrintStream(file); + + _code.printPrefixln("#include \"pt.h\""); + _code.printPrefixln("#include \"Fifo.h\""); + _code.printPrefixln("#include \"WindowedFifo.h\""); + for (String basename : x.getProcessBasenames()) { + _code.printPrefixln("#include \"" + basename + + "Wrapper.h\""); + } + + _code.println(); + _code.printPrefixln("int main(void)"); + _code.printLeftBracket(); + + //instantiate channels + for (Channel c : x.getChannelList()) { + if (c.getType().equals("fifo")) { + _code.printPrefixln("Fifo " + c.getName() + "(" + + c.getSize() * c.getTokenSize() + ");"); + } else if (c.getType().equals("wfifo")) { + _code.printPrefixln("WindowedFifo " + c.getName() + "(" + + c.getSize() * c.getTokenSize() + ");"); + } + } + _code.println(); + + //instantiate processes + for (Process p : x.getProcessList()) { + _code.printPrefix("int " + p.getName() + + "Indices[] = { "); + Vector iteratorIndex = + p.getIteratorIndices(); + if (iteratorIndex.size() < 4) { + while (iteratorIndex.size() < 4) { + iteratorIndex.add(-1); + } + } else if (iteratorIndex.size() > 4) { + new RuntimeException("Error: Currently not more than " + + "4 iterator dimensions are supported." + + "Consider revising " + p.getBasename() + + "."); + } + for (int i = 0; i < 4; i++) { + if (i < 3) { + _code.print(iteratorIndex.elementAt(i) + ", "); + } else { + _code.println(iteratorIndex.elementAt(i) + " };"); + } + } + _code.printPrefixln(p.getBasename() + "Wrapper *" + + p.getName() + " = new " + + p.getBasename() + "Wrapper(\"" + + p.getName() + "\", " + + p.getName() + "Indices);"); + } + _code.println(); + + //connect the network + for (Process p : x.getProcessList()) { + for (Port port : p.getPortList()) { + if (port.getName().equals(port.getBasename())) { + _code.printPrefixln(p.getName() + + "->_port" + port.getName() + "Fifo = &" + + port.getPeerResource().getName() + ";"); + } else { + _code.printPrefix(p.getName() + + "->_port" + port.getBasename() + + "Fifo"); + StringTokenizer tokenizer = + new StringTokenizer(port.getName(). + replaceFirst(port.getBasename(), ""), "_"); + while (tokenizer.hasMoreTokens()) { + _code.print("[" + tokenizer.nextToken() + + "]"); + } + _code.println(" = &" + + port.getPeerResource().getName() + ";"); + } + } + } + _code.println(); + + //initialize processes + for (Process p : x.getProcessList()) { + _code.printPrefixln(p.getName() + "->init();"); + } + _code.println(); + + /* + _code.printPrefix("while("); + int counter = 0; + for (Process p : x.getProcessList()) { + if (counter > 0) { + _code.printPrefix(" "); + } + _code.print("!" + p.getName() + "->isDetached()"); + if (counter++ < x.getProcessList().size() - 1) { + _code.println(" ||"); + } else { + _code.println(")"); + } + } + */ + _code.printPrefixln("bool allBlocked = false;"); + _code.printPrefixln("while(!allBlocked)"); + _code.printLeftBracket(); + _code.printPrefixln("allBlocked = true;"); + for (Process p : x.getProcessList()) { + _code.printPrefixln("if (!" + p.getName() + + "->isDetached()) {"); + //_code.printPrefixln(" " + p.getName() + "->fire();"); + _code.printPrefixln(" if (" + p.getName() + + "->fire() == PT_ENDED) {"); + _code.printPrefixln(" allBlocked = false;"); + _code.printPrefixln(" }"); + _code.printPrefixln("}"); + } + _code.printRightBracket(); + _code.println(); + + for (Process p : x.getProcessList()) { + _code.printPrefixln("delete " + p.getName() + ";"); + } + _code.println(); + _code.printPrefixln("return 0;"); + _code.printRightBracket(); + } + catch (Exception e) { + System.out.println("ProtothreadModuleVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected CodePrintStream _code = null; + protected String _dir = null; +} + diff --git a/dol/src/dol/visitor/protothread/ProtothreadProcessVisitor.java b/dol/src/dol/visitor/protothread/ProtothreadProcessVisitor.java new file mode 100644 index 0000000..e67ac18 --- /dev/null +++ b/dol/src/dol/visitor/protothread/ProtothreadProcessVisitor.java @@ -0,0 +1,343 @@ +/* $Id: ProtothreadProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.protothread; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * + */ +public class ProtothreadProcessVisitor extends PNVisitor { + + /** + * Constructor. + */ + public ProtothreadProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector processList = new Vector(); + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!processList.contains(basename)) { + processList.add(basename); + p.accept(this); + } + } + } catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + public void visitComponent(Process p) { + try { + _createCppFile(p); + _createHeaderFile(p); + _adaptSources(p); + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + private void _createCppFile(Process p) + throws IOException { + String classname = p.getBasename() + "Wrapper"; + String filename = _dir + _delimiter + classname + ".cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#include \"" + classname + ".h\""); + ps.printPrefixln("#include \"dolSupport.h\""); + ps.printPrefixln("#include "); + ps.println(); + for (SourceCode sr : p.getSrcList()) { + ps.printPrefixln("#include \"" + "../processes/" + + sr.getLocality() + "\""); + } + ps.println(); + ps.printPrefixln(classname + "::" + classname + + "(char* name, int iteratorIndex[4]) :"); + ps.printPrefixln(" ProcessWrapper(name, iteratorIndex)"); + ps.printLeftBracket(); + //ps.printPrefixln("_state = new struct _local_states;"); + //ps.printPrefixln("memcpy(_state, " + p.getBasename() + ".local, " + // + "sizeof(struct _local_states));"); + //ps.printPrefixln("memcpy(&_process, &" + p.getBasename() + // + ", sizeof(DOLProcess));"); + ps.printPrefixln("_state = (LocalState)new " + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State;"); + ps.printPrefixln("_process.init = " + p.getBasename() + "_init;"); + ps.printPrefixln("_process.fire = " + p.getBasename() + "_fire;"); + ps.printPrefixln("_process.local = _state;"); + ps.printPrefixln("_process.wptr = (void*)&_wrapper_data;"); + ps.printPrefixln("_wrapper_data.wrapper = this;"); + ps.printRightBracket(); + ps.println(); + ps.printPrefixln(classname + "::~" + classname + "()"); + ps.printLeftBracket(); + ps.printPrefixln("if (_state)"); + ps.printPrefixln(" delete (" + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State*)_state;"); + //ps.printPrefixln(" delete _state;"); + ps.printRightBracket(); + } + + /** + * + */ + private void _createHeaderFile(Process p) + throws IOException { + String classname = p.getBasename() + "Wrapper"; + String filename = _dir + _delimiter + classname + ".h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#ifndef " + classname.toUpperCase() + + "_H"); + ps.printPrefixln("#define " + classname.toUpperCase() + + "_H"); + ps.println(); + ps.printPrefixln("#include \"ProcessWrapper.h\""); + ps.printPrefixln("#include \"Fifo.h\""); + ps.printPrefixln("#include \"WindowedFifo.h\""); + ps.println(); + ps.printPrefixln("class " + classname + ";"); + ps.println(); + ps.printPrefixln("typedef struct _" + p.getBasename() + + "_data {"); + ps.printPrefixln(" int lc;"); + ps.printPrefixln(" " + classname + " *wrapper;"); + ps.printPrefixln("} " + p.getBasename() + "_data;"); + ps.println(); + ps.printPrefixln("class " + classname + + " : public ProcessWrapper"); + ps.printLeftBracket(); + ps.printPrefixln("public:"); + ps.printPrefixln(" " + classname + "(char* name, " + + "int iteratorIndex[4]);"); + ps.printPrefixln(" virtual ~" + classname + "();"); + ps.println(); + + Vector basenames = new Vector(); + for (Port port : p.getPortList()) { + if (!basenames.contains(port.getBasename())) { + basenames.add(port.getBasename()); + } else { + continue; + } + + Channel c = (Channel)port.getPeerResource(); + if (port.getName().equals(port.getBasename())) { + if (c.getType().equals("fifo")) { + ps.printPrefixln(" Fifo* _port" + port.getName() + + "Fifo;"); + } else if (c.getType().equals("wfifo")) { + ps.printPrefixln(" WindowedFifo* _port" + + port.getName() + "Fifo;"); + } + } else { + if (c.getType().equals("fifo")) { + ps.printPrefix(" Fifo* _port" + + port.getBasename() + + "Fifo"); + } else if (c.getType().equals("wfifo")) { + ps.printPrefix(" WindowedFifo* _port" + + port.getBasename() + + "Fifo"); + } + StringTokenizer tokenizer = + new StringTokenizer(port.getRange(), ";"); + while (tokenizer.hasMoreTokens()) { + ps.print("[" + tokenizer.nextToken() + + "]"); + } + ps.println(";"); + } + } + ps.println(""); + ps.printPrefixln("protected:"); + ps.printPrefixln(" struct _local_states *_state;"); + ps.printPrefixln(" " + p.getBasename() + + "_data _wrapper_data;"); + ps.printRightBracket(); + + ps.printPrefixln(";"); + ps.println(); + ps.printPrefixln("#endif"); + } + + /** + * Make modifications to source files of a process. + * Port names need to be strings for the SystemC code generation. + * Therefore, in the header files integer port names are put into + * quotation marks. + * + * @param p process whose sources should be adapted + * @throws IOException + */ + protected void _adaptSources(Process p) throws IOException { + Sed sed = new Sed(); + //modify header file + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(). + replaceAll("(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + sed.sed(processHeaderFile, + "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", + "$1 " + "static_cast<" + p.getBasename() + + "Wrapper *>((static_cast<" + p.getBasename() + + "_data *>(p->wptr))->wrapper)->_port" + + port.getBasename() + "Fifo"); + } + } + + //modify source file + for (SourceCode sr : p.getSrcList()) { + String processSourceFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(); + + String line; + StringBuffer buffer = new StringBuffer(); + FileInputStream fileInputStream = new FileInputStream( + processSourceFile); + BufferedReader reader = new BufferedReader( + new InputStreamReader(fileInputStream)); + while((line = reader.readLine()) != null) { + buffer.append(line + "\n"); + } + reader.close(); + + String file = buffer.toString(); + //insert PT_BEGIN() at beginning of fire() function + file = file.replaceAll( + "(int[ ]*" + p.getBasename() + + "_fire[ ]*\\([ ]*DOLProcess[ ]*\\*p[ ]*\\)" + + "[\\s\\S&&[^\\{]]*)\\{", + "$1" + System.getProperty("line.separator") + "{" + + System.getProperty("line.separator") + + " PT_BEGIN((pt*)(p->wptr));"); + + //replace last return statement in fire function by PT_END() + //find beginning of fire function + Matcher matcher = Pattern.compile( + "(int[ ]*" + p.getBasename() + + "_fire[ ]*\\([ ]*DOLProcess[ ]*\\*p[ ]*\\)" + + "[\\s\\S&&[^\\{]]*)\\{").matcher(file); + matcher.find(); + int i = 0; + try { + i = matcher.start(); + } catch (Exception e) { + System.out.println("Error: could not find " + + p.getBasename() + "_fire() function in " + + processSourceFile + "."); + e.printStackTrace(); + } + int openBraces = 0; //counter for open curly braces + //position of last return statement + int lastReturnStartPosition = 0; + int lastReturnEndPosition = 0; + while (i < file.length()) { + //ignore single-line comments + if (i < (file.length() - 1) && + file.substring(i, i + 2).equals("//")) { + while (!file.substring(i, i + 1).equals("\n")) { + i++; + } + } + //ignore multi-line comments + else if (i < (file.length() - 1) && + file.substring(i, i + 2).equals("/*")) { + while (!file.substring(i, i + 2).equals("*/")) { + i++; + } + } + //ignore strings + else if (file.substring(i, i + 1).equals("\"")) { + matcher = Pattern.compile("[\\s\\S&&[^\\\\]]\\\""). + matcher(file); + matcher.find(i + 1); + i = matcher.start() + 1; + } + else if (i < (file.length() - 5) && + file.substring(i, i + 6).equals("return")) { + lastReturnStartPosition = i; + while (!file.substring(i, i + 1).equals(";")) { + i++; + } + lastReturnEndPosition = i; + } else if (file.substring(i, i + 1).equals("{")) { + openBraces++; + } else if (file.substring(i, i + 1).equals("}")) { + openBraces--; + if (openBraces == 0) { + break; + } + } + i++; + } + + file = file.substring(0, lastReturnStartPosition) + "/* " + + file.substring(lastReturnStartPosition, + lastReturnEndPosition + 1) + + " (commented out by DOL) */" + + System.getProperty("line.separator") + + " PT_END((pt*)(p->wptr));" + + System.getProperty("line.separator") + + file.substring(lastReturnEndPosition + 2, + file.length()); + + BufferedWriter out = new BufferedWriter(new + FileWriter(processSourceFile)); + out.write(file); + out.close(); + } + } + + protected String _dir = null; + CodePrintStream _wrapperHeader; +} diff --git a/dol/src/dol/visitor/protothread/ProtothreadVisitor.java b/dol/src/dol/visitor/protothread/ProtothreadVisitor.java new file mode 100644 index 0000000..88937a7 --- /dev/null +++ b/dol/src/dol/visitor/protothread/ProtothreadVisitor.java @@ -0,0 +1,96 @@ +/* $Id: ProtothreadVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.protothread; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * + */ +public class ProtothreadVisitor extends PNVisitor { + + /** + * Constructor. + */ + public ProtothreadVisitor(String packageName) { + _packageName = packageName; + } + + /** + * + */ + public void visitComponent(ProcessNetwork x) { + try { + _generateDirHierarchy(); + + x.accept(new ProtothreadMakefileVisitor(_srcDir)); + x.accept(new ProtothreadModuleVisitor(_srcDir)); + x.accept(new ProtothreadProcessVisitor(_wrapperDir)); + + } catch (Exception e) { + System.out.println(" SystemC PN Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + + } + + /** + * + */ + private void _generateDirHierarchy() + throws IOException, FileNotFoundException { + + File dir = new File(_packageName); + dir.mkdirs(); + + _srcDir = _packageName + _delimiter + _srcDirName; + dir = new File(_srcDir); + dir.mkdirs(); + + _libDir = _srcDir + _delimiter + _libDirName; + dir = new File(_libDir); + dir.mkdirs(); + + _processDir = _srcDir + _delimiter + _processDirName; + dir = new File(_processDir); + dir.mkdirs(); + + _wrapperDir = _srcDir + _delimiter + _wrapperDirName; + dir = new File(_wrapperDir); + dir.mkdirs(); + + // copy library + String libraryPath = _ui.getMySystemCLib(); + libraryPath = libraryPath.replaceAll("systemC", + "protothread"); + File source = new File(libraryPath); + File destination = new File(_libDir); + new Copier().copy(source, destination); + + //copy process src code + source = new File(_srcDirName); + destination = new File(_processDir); + new Copier().copy(source, destination); + } + + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; + + protected String _libDir = ""; + protected static String _libDirName = "lib"; + + protected String _processDir = ""; + protected static String _processDirName = "processes"; + + protected String _wrapperDir = ""; + protected static String _wrapperDirName = "wrappers"; +} + diff --git a/dol/src/dol/visitor/protothread/lib/Fifo.cpp b/dol/src/dol/visitor/protothread/lib/Fifo.cpp new file mode 100644 index 0000000..767b784 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/Fifo.cpp @@ -0,0 +1,130 @@ +#include "Fifo.h" + +/** + * + */ +Fifo::Fifo(unsigned size = 18) { + //std::cout << "Create Fifo." << std::endl; + _size = size; + _buffer = new char[_size]; + _use = 0; + _tail = 0; +} + +/** + * + */ +Fifo::~Fifo() { + //std::cout << "Delete Fifo." << std::endl; + if (_buffer) { + delete _buffer; + } + _buffer = 0; + _use = 0; + _tail = 0; + //std::cout << "Deleted Fifo." << std::endl; +} + +/** + * + */ +unsigned Fifo::read(void *destination, unsigned len) { + char* buffer = (char*)destination; + unsigned read = (len <= _use ? len : 0); + //std::cout << "Try to read " << len << " bytes from Fifo." << std::endl; + + if (_tail + read <= _size) { + memcpy(buffer, _buffer + _tail, read); + } + else { + memcpy(buffer, _buffer + _tail, _size - _tail); + memcpy(buffer + _size - _tail, _buffer, read - _size + _tail); + } + + _tail = (_tail + read) % _size; + _use -= read; + //std::cout << "Read " << read << " bytes from Fifo." << std::endl; + return read; +} + +/** + * + */ +unsigned Fifo::write(const void *source, unsigned len) { + char* buffer = (char*)source; + unsigned write = (len <= unused() ? len : 0); + unsigned head = (_tail + _use) % _size; + //std::cout << "Try to write " << len << " bytes to Fifo." << std::endl; + + if (head + write <= _size) { + memcpy(_buffer + head, buffer, write); + } + else { + memcpy(_buffer + head, buffer, _size - head); + memcpy(_buffer, buffer + _size - head, write - _size + head); + } + + _use += write; + //std::cout << "Wrote " << write << " bytes to Fifo." << std::endl; + return write; +} + +/** + * + */ +unsigned Fifo::size() const { + return (_size); +} + +/** + * + */ +unsigned Fifo::unused() const { + return (_size) - _use; +} + +/** + * + */ +unsigned Fifo::used() const { + return _use; +} + +/** + * Test the implementation + */ +/* +int main() { + std::cout.width(5); + Fifo *myFifo = new Fifo(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 6; i++) { + std::cout << "write " << i << " to Fifo. "; + int write = myFifo->write(&i, sizeof(int)); + printf(" %d ", write); + if (write == sizeof(int)) { + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } else { + std::cout << std::endl; + } + } + for (int i = 0; i < 6; i++) { + int value; + int read = myFifo->read(&value, sizeof(int)); + printf(" %d ", read); + if (read == sizeof(int)) { + std::cout << "read " << value << " from Fifo "; + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } + } + } + delete myFifo; + return 0; +} +*/ diff --git a/dol/src/dol/visitor/protothread/lib/Fifo.h b/dol/src/dol/visitor/protothread/lib/Fifo.h new file mode 100644 index 0000000..a1929dd --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/Fifo.h @@ -0,0 +1,25 @@ +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include +#include + +class Fifo { + public: + Fifo(unsigned size); + virtual ~Fifo(); + + virtual unsigned read(void *destination, unsigned len); + virtual unsigned write(const void *source, unsigned len); + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + + protected: + char *_buffer; + unsigned _use; + unsigned _tail; + unsigned _size; +}; + +#endif diff --git a/dol/src/dol/visitor/protothread/lib/ProcessWrapper.cpp b/dol/src/dol/visitor/protothread/lib/ProcessWrapper.cpp new file mode 100644 index 0000000..261f65c --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/ProcessWrapper.cpp @@ -0,0 +1,68 @@ +#include "ProcessWrapper.h" + +/** + * + */ +ProcessWrapper::ProcessWrapper(char* name, int iteratorIndex[4]) { + //copy name, deliberately avoid using strlen and strcpy for code size + //minimization + int nameLength = 0; + while (name[nameLength] != 0) { + nameLength++; + } + _name = new char[nameLength + 1]; + for (int i = 0; i < nameLength; i++) { + _name[i] = name[i]; + } + + /* + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + */ + + _isDetached = false; + for (int i = 0; i < 4; i++) { + _iteratorIndex[i] = iteratorIndex[i]; + } +} + +/** + * + */ +ProcessWrapper::~ProcessWrapper() { + if (_name) { + delete _name; + } +} + +/** + * + */ +void ProcessWrapper::init() { + _process.init(&_process); +} + +/** + * + */ +int ProcessWrapper::fire() { + return _process.fire(&_process); +} + +/** + * + */ +void ProcessWrapper::detach() { + _isDetached = true; +} + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} diff --git a/dol/src/dol/visitor/protothread/lib/ProcessWrapper.h b/dol/src/dol/visitor/protothread/lib/ProcessWrapper.h new file mode 100644 index 0000000..24ef2b2 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/ProcessWrapper.h @@ -0,0 +1,25 @@ +#ifndef _PROCESSWRAPPER_H_ +#define _PROCESSWRAPPER_H_ + +#include + +class ProcessWrapper +{ + public: + ProcessWrapper(char* name, int iteratorIndex[4]); + virtual ~ProcessWrapper(); + + virtual void init(); + virtual int fire(); + virtual bool isDetached() { return _isDetached; } + virtual void detach(); + virtual int getIndex(unsigned indexNumber) const; + + protected: + char* _name;; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; +}; + +#endif diff --git a/dol/src/dol/visitor/protothread/lib/WindowedFifo.cpp b/dol/src/dol/visitor/protothread/lib/WindowedFifo.cpp new file mode 100644 index 0000000..743fc0b --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/WindowedFifo.cpp @@ -0,0 +1,203 @@ +#include "WindowedFifo.h" + +/** + * + */ +WindowedFifo::WindowedFifo(unsigned size = 20) { + //std::cout << "Create WindowedFifo." << std::endl; + _size = size; + _buffer = new char[_size]; + _head = 0; + _tail = 0; + _headRoom = 0; + _tailRoom = 0; + _use = 0; + //indicates whether Fifo is empty or full if _head == _tail + //_isFull = false; + _isHeadReserved = false; + _isTailReserved = false; +} + +/** + * + */ +WindowedFifo::~WindowedFifo() { + //std::cout << "Delete WindowedFifo." << std::endl; + if (_buffer) { + delete _buffer; + } + _buffer = 0; + _head = 0; + _tail = 0; + _use = 0; + //std::cout << "Deleted WindowedFifo." << std::endl; +} + +/** + * + */ +unsigned WindowedFifo::reserve(void** dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to reserve " << len << " bytes." << std::endl; + + //can only reserve once piece at a time + if (_isHeadReserved) { + *destination = 0; + return 0; + } + + //reserve at most as much memory as still available in the buffer + unsigned write = (len <= _size - _use ? len : _size - _use); + + if (write > 0) { + //if wrap-around in buffer: return only buffer for the + //contiguous buffer space + if (_head + write > _size) { + write = _size - _head; + } + + _headRoom = (_head + write) == _size? 0 : _head + write; + *destination = &(_buffer[_head]); + _isHeadReserved = true; + } + + //std::cout << "Reserved " << write << " bytes." << std::endl; + _writeReserve = write; + return write; +} + +/** + * + */ +void WindowedFifo::release() { + if (_isHeadReserved) { + //std::cout << "Released " << _headRoom - _head << " bytes." << std::endl; + _head = _headRoom; + _use += _writeReserve; + _isHeadReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::capture(void **dest, unsigned len) { + char** destination = (char**)dest; + //std::cout << "Attempt to capture " << len << " bytes." << std::endl; + + if (_isTailReserved) { + //std::cout << "Only one attempt to capture allowed." << std::endl; + *destination = 0; + return 0; + } + + //capture at most as much data as available in the buffer + unsigned read = (len <= _use ? len : _use); + + if (read > 0) { + //if wrap-around in buffer: return only buffer for the + //conntiguous buffer space + if (_tail + read> _size) { + read = _size - _tail; + } + + _tailRoom = (_tail + read) == _size ? 0 : _tailRoom = _tail + read; + *destination = &(_buffer[_tail]); + _isTailReserved = true; + } + + //std::cout << "Captured " << read << " bytes." << std::endl; + + _readReserve = read; + return read; +} + +/** + * + */ +void WindowedFifo::consume() { + if (_isTailReserved) { + //std::cout << "Consumed " << _tailRoom - _tail << " bytes." << std::endl; + _tail = _tailRoom; + _use -= _readReserve; + _isTailReserved = false; + } +} + +/** + * + */ +unsigned WindowedFifo::size() const { + return _size; +} + +/** + * + */ +unsigned WindowedFifo::unused() const { + return _size - _use; +} + +/** + * + */ +unsigned WindowedFifo::used() const { + return _use; +} + +/** + * Test the implementation + */ +/* +#include +#include +using namespace std; + +int main() { + WindowedFifo *myFifo = new WindowedFifo(16); + + int* buf1; + int* buf2; + int x = myFifo->reserve((void**)&buf1, 8); + *buf1 = 10; + *(buf1 + 1) = 20; + myFifo->release(); + int y = myFifo->capture((void**)&buf2, 8); + std::cout << "read " << *buf2 << " " << *(buf2 + 1) << std::endl; + myFifo->consume(); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 6; i++) { + std::cout << "write " << i << " to Fifo. "; + int write = myFifo->reserve((void**)&buf1, sizeof(int)); + if (write == sizeof(int)) { + *buf1 = i; + myFifo->release(); + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + } else { + std::cout << std::endl; + } + } + for (int i = 0; i < 16; i++) { + char* buf3; + int read = myFifo->capture((void**)&buf3, sizeof(char)); + if (read == sizeof(char)) { + std::cout << "read " << (unsigned)*buf3 << " from Fifo "; + std::cout << "used: " << std::setw(2) << myFifo->used() + << ", unused: " << std::setw(2) << myFifo->unused() + << ", size: " << std::setw(2) << myFifo->size() + << std::endl; + myFifo->consume(); + } else { + std::cout << "read nothing from Fifo." << std::endl; + } + + } + } + delete myFifo; + return 0; +} +*/ diff --git a/dol/src/dol/visitor/protothread/lib/WindowedFifo.h b/dol/src/dol/visitor/protothread/lib/WindowedFifo.h new file mode 100644 index 0000000..2803558 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/WindowedFifo.h @@ -0,0 +1,32 @@ +#ifndef _WINDOWEDFIFO_H_ +#define _WINDOWEDFIFO_H_ + +class WindowedFifo { + public: + WindowedFifo(unsigned size); + virtual ~WindowedFifo(); + + virtual unsigned reserve(void** destination, unsigned len); + virtual void release(); + + virtual unsigned capture(void** destination, unsigned len); + virtual void consume(); + + virtual unsigned used() const; + virtual unsigned unused() const; + virtual unsigned size() const; + protected: + char *_buffer; + unsigned _head; + unsigned _tail; + unsigned _headRoom; + unsigned _tailRoom; + unsigned _size; + unsigned _use; + unsigned _writeReserve; + unsigned _readReserve; + bool _isHeadReserved; + bool _isTailReserved; +}; + +#endif diff --git a/dol/src/dol/visitor/protothread/lib/dol.h b/dol/src/dol/visitor/protothread/lib/dol.h new file mode 100644 index 0000000..8fbefe4 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/dol.h @@ -0,0 +1,39 @@ +#ifndef DOL_H +#define DOL_H + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * Define the DOL process handler scheme. + * - Local variables are defined in structure LocalState. Local + * variables may vary from different processes. + * - The ProcessInit function pointer points to a function which + * initializes a process. + * - The ProcessFire function pointer points to a function which + * performs the actual computation. The communication between + * processes is inside the ProcessFire function. + * - The WPTR is a placeholder for callback. One can just + * leave it blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + +#endif diff --git a/dol/src/dol/visitor/protothread/lib/dolSupport.cpp b/dol/src/dol/visitor/protothread/lib/dolSupport.cpp new file mode 100644 index 0000000..0c73f6d --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/dolSupport.cpp @@ -0,0 +1,89 @@ +#include "dolSupport.h" + +/** + * + */ +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p) { + return ((Fifo*)fifo)->read(buf, len); +} + +/** + * + */ +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p) { + return ((Fifo*)fifo)->write(buf, len); +} + +/** + * + */ +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->reserve(destination, len); +} + +/** + * + */ +void release(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->release(); +} + +/** + * + */ +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p) { + return ((WindowedFifo*)fifo)->capture(destination, len); +} + +/** + * + */ +void consume(void* fifo, DOLProcess* p) { + ((WindowedFifo*)fifo)->consume(); +} + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast((static_cast(p->wptr))->wrapper)->detach(); +} + + +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0) { + *port = (void**)((void**)base)[index0]; +} + +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1) { + *port = (void**)((void**)base)[index0 * range1 + index1]; +} + +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2) { + *port = (void**)((void**)base)[index0 * range1 * range2 + + index1 * range2 + index2]; +} + +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3) { + *port = (void**)((void**)base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + + index2 * range3 + + index3]; +} diff --git a/dol/src/dol/visitor/protothread/lib/dolSupport.h b/dol/src/dol/visitor/protothread/lib/dolSupport.h new file mode 100644 index 0000000..f07a804 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/dolSupport.h @@ -0,0 +1,76 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include "dol.h" +#include "ProcessWrapper.h" +#include "Fifo.h" +#include "WindowedFifo.h" +#include "pt.h" + +typedef struct _process_data { + int lc; + ProcessWrapper *wrapper; +} process_data; + + +#define DOL_read(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), read(port, buf, size, process) == size); + +#define DOL_write(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), write(port, buf, size, process) == size); + +#define DOL_reserve(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), reserve(port, (void**)buf, size, process) == size); + +#define DOL_release(port, process) \ + release(port, process); + +#define DOL_capture(port, buf, size, process) \ + PT_WAIT_UNTIL((pt*)(p->wptr), capture(port, (void**)buf, size, process) == size); + +#define DOL_consume(port, process) \ + consume(port, process); + +void DOL_detach(DOLProcess* p); + +//macros to deal with iterated ports +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) static Fifo *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#define GETINDEX(dimension) \ + static_cast((static_cast(p->wptr))->wrapper)->getIndex(dimension) + +void createPort(void** port, void* base, int number_of_indices, int index0, int range0); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2); +void createPort(void** port, void* base, int number_of_indices, int index0, int range0, int index1, int range1, int index2, int range2, int index3, int range3); + +//fifo access functions +unsigned write(void* fifo, void* buf, unsigned len, DOLProcess* p); +unsigned read(void* fifo, void* buf, unsigned len, DOLProcess* p); + +//windowed fifo access functions +unsigned reserve(void* fifo, void** destination, unsigned len, DOLProcess* p); +void release(void* fifo, DOLProcess* p); +unsigned capture(void* fifo, void** destination, unsigned len, DOLProcess* p); +void consume(void* fifo, DOLProcess* p); + +#endif diff --git a/dol/src/dol/visitor/protothread/lib/lc-addrlabels.h b/dol/src/dol/visitor/protothread/lib/lc-addrlabels.h new file mode 100644 index 0000000..b75f4e7 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/lc-addrlabels.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: lc-addrlabels.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on the "Labels as + * values" feature of gcc + * \author + * Adam Dunkels + * + * This implementation of local continuations is based on a special + * feature of the GCC C compiler called "labels as values". This + * feature allows assigning pointers with the address of the code + * corresponding to a particular C label. + * + * For more information, see the GCC documentation: + * http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html + * + */ + +#ifndef __LC_ADDRLABELS_H__ +#define __LC_ADDRLABELS_H__ + +/** \hideinitializer */ +typedef void * lc_t; + +#define LC_INIT(s) s = NULL + +#define LC_RESUME(s) \ + do { \ + if(s != NULL) { \ + goto *s; \ + } \ + } while(0) + +#define LC_CONCAT2(s1, s2) s1##s2 +#define LC_CONCAT(s1, s2) LC_CONCAT2(s1, s2) + +#define LC_SET(s) \ + do { \ + LC_CONCAT(LC_LABEL, __LINE__): \ + (s) = &&LC_CONCAT(LC_LABEL, __LINE__); \ + } while(0) + +#define LC_END(s) + +#endif /* __LC_ADDRLABELS_H__ */ +/** @} */ diff --git a/dol/src/dol/visitor/protothread/lib/lc-switch.h b/dol/src/dol/visitor/protothread/lib/lc-switch.h new file mode 100644 index 0000000..e47085c --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/lc-switch.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: lc-switch.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on switch() statment + * \author Adam Dunkels + * + * This implementation of local continuations uses the C switch() + * statement to resume execution of a function somewhere inside the + * function's body. The implementation is based on the fact that + * switch() statements are able to jump directly into the bodies of + * control structures such as if() or while() statmenets. + * + * This implementation borrows heavily from Simon Tatham's coroutines + * implementation in C: + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + */ + +#ifndef __LC_SWITCH_H__ +#define __LC_SWITCH_H__ + +/* WARNING! lc implementation using switch() does not work if an + LC_SET() is done within another switch() statement! */ + +/** \hideinitializer */ +typedef unsigned short lc_t; + +#define LC_INIT(s) s = 0; + +#define LC_RESUME(s) switch(s) { case 0: + +#define LC_SET(s) s = __LINE__; case __LINE__: + +#define LC_END(s) } + +#endif /* __LC_SWITCH_H__ */ + +/** @} */ diff --git a/dol/src/dol/visitor/protothread/lib/lc.h b/dol/src/dol/visitor/protothread/lib/lc.h new file mode 100644 index 0000000..9ef2f0f --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/lc.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the protothreads library. + * + * Author: Adam Dunkels + * + * $Id: lc.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \defgroup lc Local continuations + * @{ + * + * Local continuations form the basis for implementing protothreads. A + * local continuation can be set in a specific function to + * capture the state of the function. After a local continuation has + * been set can be resumed in order to restore the state of the + * function at the point where the local continuation was set. + * + * + */ + +/** + * \file lc.h + * Local continuations + * \author + * Adam Dunkels + * + */ + +#ifdef DOXYGEN +/** + * Initialize a local continuation. + * + * This operation initializes the local continuation, thereby + * unsetting any previously set continuation state. + * + * \hideinitializer + */ +#define LC_INIT(lc) + +/** + * Set a local continuation. + * + * The set operation saves the state of the function at the point + * where the operation is executed. As far as the set operation is + * concerned, the state of the function does not include the + * call-stack or local (automatic) variables, but only the program + * counter and such CPU registers that needs to be saved. + * + * \hideinitializer + */ +#define LC_SET(lc) + +/** + * Resume a local continuation. + * + * The resume operation resumes a previously set local continuation, thus + * restoring the state in which the function was when the local + * continuation was set. If the local continuation has not been + * previously set, the resume operation does nothing. + * + * \hideinitializer + */ +#define LC_RESUME(lc) + +/** + * Mark the end of local continuation usage. + * + * The end operation signifies that local continuations should not be + * used any more in the function. This operation is not needed for + * most implementations of local continuation, but is required by a + * few implementations. + * + * \hideinitializer + */ +#define LC_END(lc) + +/** + * \var typedef lc_t; + * + * The local continuation type. + * + * \hideinitializer + */ +#endif /* DOXYGEN */ + +#ifndef __LC_H__ +#define __LC_H__ + + +#ifdef LC_INCLUDE +#include LC_INCLUDE +#else +#include "lc-switch.h" +#endif /* LC_INCLUDE */ + +#endif /* __LC_H__ */ + +/** @} */ +/** @} */ diff --git a/dol/src/dol/visitor/protothread/lib/pt-sem.h b/dol/src/dol/visitor/protothread/lib/pt-sem.h new file mode 100644 index 0000000..a6e1428 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/pt-sem.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the protothreads library. + * + * Author: Adam Dunkels + * + * $Id: pt-sem.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \defgroup ptsem Protothread semaphores + * @{ + * + * This module implements counting semaphores on top of + * protothreads. Semaphores are a synchronization primitive that + * provide two operations: "wait" and "signal". The "wait" operation + * checks the semaphore counter and blocks the thread if the counter + * is zero. The "signal" operation increases the semaphore counter but + * does not block. If another thread has blocked waiting for the + * semaphore that is signalled, the blocked thread will become + * runnable again. + * + * Semaphores can be used to implement other, more structured, + * synchronization primitives such as monitors and message + * queues/bounded buffers (see below). + * + * The following example shows how the producer-consumer problem, also + * known as the bounded buffer problem, can be solved using + * protothreads and semaphores. Notes on the program follow after the + * example. + * + \code +#include "pt-sem.h" + +#define NUM_ITEMS 32 +#define BUFSIZE 8 + +static struct pt_sem mutex, full, empty; + +PT_THREAD(producer(struct pt *pt)) +{ + static int produced; + + PT_BEGIN(pt); + + for(produced = 0; produced < NUM_ITEMS; ++produced) { + + PT_SEM_WAIT(pt, &full); + + PT_SEM_WAIT(pt, &mutex); + add_to_buffer(produce_item()); + PT_SEM_SIGNAL(pt, &mutex); + + PT_SEM_SIGNAL(pt, &empty); + } + + PT_END(pt); +} + +PT_THREAD(consumer(struct pt *pt)) +{ + static int consumed; + + PT_BEGIN(pt); + + for(consumed = 0; consumed < NUM_ITEMS; ++consumed) { + + PT_SEM_WAIT(pt, &empty); + + PT_SEM_WAIT(pt, &mutex); + consume_item(get_from_buffer()); + PT_SEM_SIGNAL(pt, &mutex); + + PT_SEM_SIGNAL(pt, &full); + } + + PT_END(pt); +} + +PT_THREAD(driver_thread(struct pt *pt)) +{ + static struct pt pt_producer, pt_consumer; + + PT_BEGIN(pt); + + PT_SEM_INIT(&empty, 0); + PT_SEM_INIT(&full, BUFSIZE); + PT_SEM_INIT(&mutex, 1); + + PT_INIT(&pt_producer); + PT_INIT(&pt_consumer); + + PT_WAIT_THREAD(pt, producer(&pt_producer) & + consumer(&pt_consumer)); + + PT_END(pt); +} + \endcode + * + * The program uses three protothreads: one protothread that + * implements the consumer, one thread that implements the producer, + * and one protothread that drives the two other protothreads. The + * program uses three semaphores: "full", "empty" and "mutex". The + * "mutex" semaphore is used to provide mutual exclusion for the + * buffer, the "empty" semaphore is used to block the consumer is the + * buffer is empty, and the "full" semaphore is used to block the + * producer is the buffer is full. + * + * The "driver_thread" holds two protothread state variables, + * "pt_producer" and "pt_consumer". It is important to note that both + * these variables are declared as static. If the static + * keyword is not used, both variables are stored on the stack. Since + * protothreads do not store the stack, these variables may be + * overwritten during a protothread wait operation. Similarly, both + * the "consumer" and "producer" protothreads declare their local + * variables as static, to avoid them being stored on the stack. + * + * + */ + +/** + * \file + * Couting semaphores implemented on protothreads + * \author + * Adam Dunkels + * + */ + +#ifndef __PT_SEM_H__ +#define __PT_SEM_H__ + +#include "pt.h" + +struct pt_sem { + unsigned int count; +}; + +/** + * Initialize a semaphore + * + * This macro initializes a semaphore with a value for the + * counter. Internally, the semaphores use an "unsigned int" to + * represent the counter, and therefore the "count" argument should be + * within range of an unsigned int. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \param c (unsigned int) The initial count of the semaphore. + * \hideinitializer + */ +#define PT_SEM_INIT(s, c) (s)->count = c + +/** + * Wait for a semaphore + * + * This macro carries out the "wait" operation on the semaphore. The + * wait operation causes the protothread to block while the counter is + * zero. When the counter reaches a value larger than zero, the + * protothread will continue. + * + * \param pt (struct pt *) A pointer to the protothread (struct pt) in + * which the operation is executed. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \hideinitializer + */ +#define PT_SEM_WAIT(pt, s) \ + do { \ + PT_WAIT_UNTIL(pt, (s)->count > 0); \ + --(s)->count; \ + } while(0) + +/** + * Signal a semaphore + * + * This macro carries out the "signal" operation on the semaphore. The + * signal operation increments the counter inside the semaphore, which + * eventually will cause waiting protothreads to continue executing. + * + * \param pt (struct pt *) A pointer to the protothread (struct pt) in + * which the operation is executed. + * + * \param s (struct pt_sem *) A pointer to the pt_sem struct + * representing the semaphore + * + * \hideinitializer + */ +#define PT_SEM_SIGNAL(pt, s) ++(s)->count + +#endif /* __PT_SEM_H__ */ + +/** @} */ +/** @} */ + diff --git a/dol/src/dol/visitor/protothread/lib/pt.h b/dol/src/dol/visitor/protothread/lib/pt.h new file mode 100644 index 0000000..a7d3829 --- /dev/null +++ b/dol/src/dol/visitor/protothread/lib/pt.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * Author: Adam Dunkels + * + * $Id: pt.h 1 2010-02-24 13:03:05Z haidw $ + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \file + * Protothreads implementation. + * \author + * Adam Dunkels + * + */ + +#ifndef __PT_H__ +#define __PT_H__ + +#include "lc.h" + +typedef struct _pt { + lc_t lc; +} pt; + +#define PT_WAITING 0 +#define PT_YIELDED 1 +#define PT_EXITED 2 +#define PT_ENDED 3 + +/** + * \name Initialization + * @{ + */ + +/** + * Initialize a protothread. + * + * Initializes a protothread. Initialization must be done prior to + * starting to execute the protothread. + * + * \param pt A pointer to the protothread control structure. + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_INIT(pt) LC_INIT((pt)->lc) + +/** @} */ + +/** + * \name Declaration and definition + * @{ + */ + +/** + * Declaration of a protothread. + * + * This macro is used to declare a protothread. All protothreads must + * be declared with this macro. + * + * \param name_args The name and arguments of the C function + * implementing the protothread. + * + * \hideinitializer + */ +#define PT_THREAD(name_args) char name_args + +/** + * Declare the start of a protothread inside the C function + * implementing the protothread. + * + * This macro is used to declare the starting point of a + * protothread. It should be placed at the start of the function in + * which the protothread runs. All C statements above the PT_BEGIN() + * invokation will be executed each time the protothread is scheduled. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) + +/** + * Declare the end of a protothread. + * + * This macro is used for declaring that a protothread ends. It must + * always be used together with a matching PT_BEGIN() macro. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ + PT_INIT(pt); return PT_ENDED; } + +/** @} */ + +/** + * \name Blocked wait + * @{ + */ + +/** + * Block and wait until condition is true. + * + * This macro blocks the protothread until the specified condition is + * true. + * + * \param pt A pointer to the protothread control structure. + * \param condition The condition. + * + * \hideinitializer + */ +#define PT_WAIT_UNTIL(pt, condition) \ + do { \ + LC_SET((pt)->lc); \ + if(!(condition)) { \ + return PT_WAITING; \ + } \ + } while(0) + +/** + * Block and wait while condition is true. + * + * This function blocks and waits while condition is true. See + * PT_WAIT_UNTIL(). + * + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * \hideinitializer + */ +#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) + +/** @} */ + +/** + * \name Hierarchical protothreads + * @{ + */ + +/** + * Block and wait until a child protothread completes. + * + * This macro schedules a child protothread. The current protothread + * will block until the child protothread completes. + * + * \note The child protothread must be manually initialized with the + * PT_INIT() function before this function is used. + * + * \param pt A pointer to the protothread control structure. + * \param thread The child protothread with arguments + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) + +/** + * Spawn a child protothread and wait until it exits. + * + * This macro spawns a child protothread and waits until it exits. The + * macro can only be used within a protothread. + * + * \param pt A pointer to the protothread control structure. + * \param child A pointer to the child protothread's control structure. + * \param thread The child protothread with arguments + * + * \hideinitializer + */ +#define PT_SPAWN(pt, child, thread) \ + do { \ + PT_INIT((child)); \ + PT_WAIT_THREAD((pt), (thread)); \ + } while(0) + +/** @} */ + +/** + * \name Exiting and restarting + * @{ + */ + +/** + * Restart the protothread. + * + * This macro will block and cause the running protothread to restart + * its execution at the place of the PT_BEGIN() call. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_RESTART(pt) \ + do { \ + PT_INIT(pt); \ + return PT_WAITING; \ + } while(0) + +/** + * Exit the protothread. + * + * This macro causes the protothread to exit. If the protothread was + * spawned by another protothread, the parent protothread will become + * unblocked and can continue to run. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_EXIT(pt) \ + do { \ + PT_INIT(pt); \ + return PT_EXITED; \ + } while(0) + +/** @} */ + +/** + * \name Calling a protothread + * @{ + */ + +/** + * Schedule a protothread. + * + * This function shedules a protothread. The return value of the + * function is non-zero if the protothread is running or zero if the + * protothread has exited. + * + * \param f The call to the C function implementing the protothread to + * be scheduled + * + * \hideinitializer + */ +#define PT_SCHEDULE(f) ((f) < PT_EXITED) + +/** @} */ + +/** + * \name Yielding from a protothread + * @{ + */ + +/** + * Yield from the current protothread. + * + * This function will yield the protothread, thereby allowing other + * processing to take place in the system. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_YIELD(pt) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if(PT_YIELD_FLAG == 0) { \ + return PT_YIELDED; \ + } \ + } while(0) + +/** + * \brief Yield from the protothread until a condition occurs. + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * This function will yield the protothread, until the + * specified condition evaluates to true. + * + * + * \hideinitializer + */ +#define PT_YIELD_UNTIL(pt, cond) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if((PT_YIELD_FLAG == 0) || !(cond)) { \ + return PT_YIELDED; \ + } \ + } while(0) + +/** @} */ + +#endif /* __PT_H__ */ + +/** @} */ diff --git a/dol/src/dol/visitor/rtems/RtemsMakefileVisitor.java b/dol/src/dol/visitor/rtems/RtemsMakefileVisitor.java new file mode 100644 index 0000000..04c617e --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsMakefileVisitor.java @@ -0,0 +1,296 @@ +/* $Id: RtemsMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.rtems; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.Configuration; +import dol.main.UserInterface; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a RTEMS package Makefile. + */ +public class RtemsMakefileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public RtemsMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param pn process network + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + _ui = UserInterface.getInstance(); + if (_ui.getRtemsBSP().equals("pc386")) { + ps.println(getPc386Makefile(pn)); + } else if (_ui.getRtemsBSP().equals("mparm")) { + ps.println(getMparmMakefile(pn)); + } + } catch (Exception e) { + System.out.println("RtemsMakefileVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create a makefile for the pc386 board support package. + * + * @param pn process network + * @return makefile for pc386 board support package + */ + protected String getMparmMakefile(ProcessNetwork pn) { + String makefile = ""; + String newline = System.getProperty("line.separator"); + makefile += "ifndef SWARMDIR" + newline; + makefile += " $(error Fatal error: Undefined SWARMDIR " + + "environment variable!)" + newline; + makefile += "endif" + newline; + makefile += "" + newline; + makefile += "ifndef RTEMS_MAKEFILE_PATH" + newline; + makefile += " $(error Fatal error: Undefined " + + "RTEMS_MAKEFILE_PATH environment variable!)" + newline; + makefile += "endif" + newline; + makefile += "" + newline; + makefile += "ifdef EXENAME" + newline; + makefile += " EXEC=$(EXENAME).exe" + newline; + makefile += "else" + newline; + makefile += " EXEC=app.exe" + newline; + makefile += "endif" + newline; + makefile += "" + newline; + makefile += "PGM=${ARCH}/$(EXEC)" + newline; + makefile += "" + newline; + makefile += "# optional managers required" + newline; + makefile += "MANAGERS=io mp msg signal event region partition"; + makefile += newline + newline; + + if (_ui.getRtemsBSP().equals("mparm")) { + makefile += "# scratchpad queue lib" + newline; + makefile += "QUEUELIB=que_lib/lib" + newline; + makefile += "SS_SEMAPHORE_LIB_PATH = " + + "${QUEUELIB}/ss_semaphore_lib" + newline; + makefile +="SCRATCH_QUEUE_LIB_PATH = " + + "${QUEUELIB}/scratch_queue_lib" + newline; + makefile += "SCRATCH_SEMAPHORE_LIB_PATH = " + + "${QUEUELIB}/scratch_semaphore_lib" + newline; + makefile += "EXT_INT_LIB_PATH = ${QUEUELIB}/ext_int_lib" + newline; + } + + makefile += "" + newline; + makefile += "#H_files" + newline; + makefile += "H_FILES=buffer_test_io.h" + newline; + + if (_ui.getRtemsBSP().equals("mparm")) { + makefile += "H_FILES += system.h tmacros.h" + newline; + makefile += newline; + makefile += "COMMON_FLAGS += -DMPARM" + newline; + makefile += "COMMON_FLAGS += -I$(QUEUELIB)" + newline; + makefile += "COMMON_FLAGS += -I$(SS_SEMAPHORE_LIB_PATH)" + + newline; + makefile += "COMMON_FLAGS += -I$(SCRATCH_QUEUE_LIB_PATH)" + + newline; + makefile += "COMMON_FLAGS += -I$(SCRATCH_SEMAPHORE_LIB_PATH)"; + makefile += newline; + makefile += newline; + makefile += "# communication options" + newline; + makefile += "COMMON_FLAGS += -DMPARM_SCRATCHPAD_QUEUE" + + newline; + makefile+="COMMON_FLAGS += -DQUEUE_BUFF_IN_PRODUCER " + + "-Dshaper_PROCESSOR" + newline; + makefile+="#COMMON_FLAGS += -DQUEUE_BUFF_IN_PRODUCER_DMA " + + "-Dshaper_PROCESSOR" + newline; + makefile+="#COMMON_FLAGS += -DQUEUE_BUFF_IN_CONSUMER " + + "-Dshaper_PROCESSOR" + newline; + makefile+="#COMMON_FLAGS += -DQUEUE_BUFF_IN_CONSUMER_DMA " + + "-Dshaper_PROCESSOR" + newline; + makefile+="#COMMON_FLAGS += -DQUEUE_BUFF_IN_SHARDMEM " + + "-Dshaper_PROCESSOR" + newline; + + int shaperProcessorID; + if (_ui.getMappingFileName() == null) { + shaperProcessorID = pn.getProcessList().size(); + } else { + ArchiXmlParser archParser = new ArchiXmlParser(); + Architecture arch = archParser. + doParse(_ui.getPlatformFileName()); + MapXmlParser mappingParser = new MapXmlParser(pn, arch); + Mapping mapping = mappingParser. + doParse(_ui.getMappingFileName()); + shaperProcessorID = mapping.getProcessorList().size(); + } + makefile +="#COMMON_FLAGS += -DQUEUE_BUFF_SHAPER " + + "-Dshaper_PROCESSOR=" + shaperProcessorID + newline; + + makefile += newline; + makefile += "LIBFILECXX = " + + "$(SS_SEMAPHORE_LIB_PATH)/ss_semaphore.cpp " + + "$(SCRATCH_QUEUE_LIB_PATH)/scratch_queue.cpp" + + newline; + makefile += "VPATH = SCRATCH_QUEUE_LIB_PATH" + newline; + } + + makefile += "" + newline; + makefile += "# C source names" + newline; + if (_ui.getRtemsBSP().equals("pc386")) { + makefile += "CSRCS = main.c rtems_process_wrapper.c "; + for (String basename : pn.getProcessBasenames()) { + makefile += basename + "_wrapper.c "; + } + } + makefile += newline; + makefile += "COBJS_ = $(CSRCS:.c=.o)" + newline; + makefile += "COBJS = $(COBJS_:%=%)" + newline; + makefile += "" + newline; + makefile += "# C++ source names" + newline; + makefile += "CXXSRCS = appsupport.c" + newline; + + if (_ui.getRtemsBSP().equals("mparm")) { + makefile += "CXXSRCS += main.c traffic_shaping.c " + + "rtems_process_wrapper.c "; + Vector pList = new Vector(); + for (Process p : pn.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + makefile += p.getBasename() + "_wrapper.c "; + pList.add(basename); + } + } + makefile += newline; + makefile += "CXXSRCS += $(LIBFILECXX)" + newline; + } + + makefile += newline; + makefile += newline; + makefile += "CXXOBJS_ = $(CXXSRCS:.cpp=.o)" + newline; + makefile += "CXXOBJS = $(CXXOBJS_:%=%)" + newline; + makefile += "" + newline; + makefile += "# AS source names" + newline; + makefile += "ASSRCS =" + newline; + makefile += "ASOBJS_ = $(ASSRCS:.s=.o)" + newline; + makefile += "ASOBJS = $(ASOBJS_:%=%)" + newline; + makefile += "" + newline; + makefile += "# Libraries" + newline; + makefile += "LIBS = -lrtemsall -lc" + newline; + makefile += "" + newline; + makefile += "include $(RTEMS_MAKEFILE_PATH)/Makefile.inc" + + newline; + makefile += "include $(RTEMS_CUSTOM)" + newline; + makefile += "include $(PROJECT_ROOT)/make/leaf.cfg" + newline; + makefile += "" + newline; + makefile += "#CXXFLAGS += -DAUTOSTARTMEASURING" + newline; + makefile += "#CXXFLAGS += -DVERBOSE" + newline; + makefile += "CXXFLAGS += -I$(SWARMDIR)/core" + newline; + makefile += "CFLAGS += -I$(SWARMDIR)/core" + newline; + + if (_ui.getRtemsBSP().equals("mparm")) { + makefile += "CXXFLAGS += $(COMMON_FLAGS) -I${PWD}" + newline; + makefile += "CFLAGS += $(COMMON_FLAGS) -I${PWD}" + newline; + + makefile += newline; + makefile += "#### for calibration" + newline; + makefile += "CXXFLAGS += -Ilib" + newline; + makefile += "CXXFLAGS += -D_GLIBCPP_HAVE_WCHAR_H " + + "-D_GLIBCPP_HAVE_MBSTATE_T" + newline; + makefile += "CXXFLAGS += -DWORKLOAD_EXTRACT" + newline; + makefile += "CXXFLAGS += -DPRINTF_TO_DEBUG" + newline; + } + makefile += "" + newline; + makefile += "SRCS=$(H_FILES)" + newline; + makefile += "OBJS= $(COBJS) $(CXXOBJS) $(ASOBJS)" + newline; + makefile += "" + newline; + makefile += "all: ${ARCH} $(SRCS) $(PGM) " + newline; + makefile += "" + newline; + makefile += "$(PGM): $(OBJS) " + newline; + makefile += "\t$(make-exe)" + newline; + makefile += "clean:" + newline; + makefile += "\t-rm -f ${SS_SEMAPHORE_LIB_PATH}/*.o " + + "${SCRATCH_QUEUE_LIB_PATH}/*.o" + newline; + + return makefile; + } + + /** + * Create a makefile for the pc386 board support package. + * + * @param pn process network + * @return makefile for pc386 board support package + */ + protected String getPc386Makefile(ProcessNetwork pn) { + String makefile = ""; + String newline = System.getProperty("line.separator"); + makefile += "EXEC=main.exe" + newline; + makefile += "PGM=${ARCH}/$(EXEC)" + newline; + makefile += newline; + makefile += "# optional managers required" + newline; + makefile += "MANAGERS=all" + newline; + makefile += newline; + makefile += "# C source names" + newline; + makefile += "CSRCS = main.c rtems_process_wrapper.c "; + for (String basename : pn.getProcessBasenames()) { + makefile += basename + "_wrapper.c "; + } + for (Configuration conf : pn.getCfgList()) { + if (conf.getName().equals("EXTERNAL_SRC")) { + makefile += conf.getValue(); + } + } + makefile += newline; + makefile += "COBJS_ = $(CSRCS:.c=.o)" + newline; + makefile += "COBJS = $(COBJS_:%=${ARCH}/%)" + newline; + makefile += newline; + makefile += "# C++ source names" + newline; + makefile += "CXXSRCS =" + newline; + makefile += "CXXOBJS_ = $(CXXSRCS:.cc=.o)" + newline; + makefile += "CXXOBJS = $(CXXOBJS_:%=${ARCH}/%)" + newline; + makefile += newline; + makefile += "# AS source names" + newline; + makefile += "ASSRCS =" + newline; + makefile += "ASOBJS_ = $(ASSRCS:.s=.o)" + newline; + makefile += "ASOBJS = $(ASOBJS_:%=${ARCH}/%)" + newline; + makefile += newline; + makefile += "# Libraries" + newline; + makefile += "LIBS = -lrtemsall -lc "; + for (Configuration conf : pn.getCfgList()) { + if (conf.getName().equals("DYNAMIC_LINK")) + makefile += conf.getValue() + newline; + } + makefile += newline; + makefile += "include $(RTEMS_MAKEFILE_PATH)/Makefile.inc" + + newline; + makefile += newline; + makefile += "include $(RTEMS_CUSTOM)" + newline; + makefile += "include $(PROJECT_ROOT)/make/leaf.cfg" + newline; + makefile += newline; + makefile += "OBJS= $(COBJS) $(CXXOBJS) $(ASOBJS)" + newline; + makefile += newline; + makefile += "all: ${ARCH} $(PGM)" + newline; + makefile += newline; + makefile += "$(PGM): $(OBJS)" + newline; + makefile += "\t$(make-exe)" + newline; + return makefile; + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/rtems/RtemsModuleVisitor.java b/dol/src/dol/visitor/rtems/RtemsModuleVisitor.java new file mode 100644 index 0000000..d62eda3 --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsModuleVisitor.java @@ -0,0 +1,864 @@ +/* $Id: RtemsModuleVisitor.java 114 2010-07-05 07:47:02Z haidw $ */ +package dol.visitor.rtems; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.mapping.Schedule; +import dol.datamodel.mapping.ScheduleEntry; +import dol.datamodel.mapping.SchedulingPolicy; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.main.UserInterface; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + */ +public class RtemsModuleVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public RtemsModuleVisitor(String dir, HashMap portMap) { + _dir = dir; + _portMap = portMap; + } + + /** + * Visit process network. + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + _ui = UserInterface.getInstance(); + String filename = _dir + _delimiter + "main.c"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + Vector pList = x.getProcessList(); + Vector processList = new Vector(); + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!processList.contains(basename)) { + processList.add(basename); + } + } + + Architecture arch = null; + Mapping mapping = null; + + //create header section + if (_ui.getRtemsBSP().equals("pc386")) { + _mainPS.println("#include "); + _mainPS.println("#include "); + _mainPS.println("#include "); + _mainPS.println("#include "); + _mainPS.println(); + _mainPS.println("rtems_task Init(rtems_task_argument argument);"); + _mainPS.println(); + _mainPS.println("#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER"); + _mainPS.println("#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER"); + _mainPS.println("#define CONFIGURE_RTEMS_INIT_TASKS_TABLE"); + _mainPS.println("#define CONFIGURE_MAXIMUM_TASKS " + + (x.getProcessList().size() + 2)); + _mainPS.println("#define CONFIGURE_MAXIMUM_MESSAGE_QUEUES " + + x.getChannelList().size()); + _mainPS.println("#define CONFIGURE_INIT"); + _mainPS.println("#include "); + _mainPS.println(); + } else if (_ui.getRtemsBSP().equals("mparm")) { + _mainPS.println("#define TEST_INIT"); + _mainPS.println("#include "); + _mainPS.println("#include "); + _mainPS.println("#include "); + _mainPS.println(); + _mainPS.println("#define RTEMS_TRACE_MAIN_APP"); + _mainPS.println("#include \"system.h\""); + _mainPS.println("#include \"appsupport.h\""); + _mainPS.println("#include \"scratch_queue.h\""); + _mainPS.println(); + } + + _mainPS.println("#include \"dol.h\""); + _mainPS.println("#include \"rtems_process_wrapper.h\""); + _mainPS.println(); + + if (_ui.getRtemsBSP().equals("pc386")) { + _mainPS.println("rtems_task CleanupTask(rtems_task_argument arg);"); + _mainPS.println("rtems_id queue_id[" + + x.getChannelList().size() + "];"); + _mainPS.println(); + } else if (_ui.getRtemsBSP().equals("mparm")) { + //if no mapping is provided, map each process to a new + //processor + //shaper + _mainPS.println("#ifdef QUEUE_BUFF_SHAPER"); + _mainPS.println("#include \"traffic_shaping.h\""); + //_mainPS.println("#define shaper_PROCESSOR " + // + (pList.size()+1)); + _mainPS.println("#endif // shaper"); + _mainPS.println(); + + if (_ui.getMappingFileName() == null) { + + int i = 0; + for (Process p : pList) { + _mainPS.println("#define " + p.getName() + + "_PROCESSOR " + ++i); //count from 1 + } + _mainPS.println(); + + _mainPS.print("unsigned int number_of_processes[" + + pList.size() + "] = { "); + for (i = 0; i < pList.size(); i++) { + _mainPS.print("1, "); + } + _mainPS.println("};"); + _mainPS.println(); + + } else { //map processes according to mapping file + ArchiXmlParser archParser = new ArchiXmlParser(); + arch = archParser.doParse(_ui.getPlatformFileName()); + MapXmlParser mappingParser = new MapXmlParser(x, arch); + mapping = mappingParser.doParse(_ui.getMappingFileName()); + int numOfUsedProcessors = mapping.getProcessorList().size(); + int numOfProcessors = arch.getProcessorList().size(); + int processesPerProcessor[] = new int[numOfProcessors]; + + // need to check processors used start from 0 and + //continuous + for (Process p : pList) { + for (ComputationBinding b : mapping.getCompBindList()){ + int processorIndex = Integer.valueOf( + b.getProcessor().getName(). + replaceAll(".*_", "")); + if (b.getProcess().getName(). + equals(p.getName())) { + processesPerProcessor[processorIndex]++; + //processor indices start at 1 in MPARM + _mainPS.println("#define " + p.getName() + + "_PROCESSOR " + + (processorIndex + 1)); + } + } + } + + + for (int i=0; i < numOfUsedProcessors; i++) { + if (processesPerProcessor[i] < 1) + throw new Exception("No process is mapped to " + + "PROCESSOR[" + i + "]. For the MPARM " + + "platform, processors should be used " + + "starting from 0 until N (in the " + + "generated code, from 1 to N + 1), and " + + "every processor between 0 and N " + + "should have at least one process " + + "mapped on it!"); + } + + _mainPS.println(); + /* + _mainPS.println("#ifdef QUEUE_BUFF_SHAPER"); + _mainPS.println("#define shaper_PROCESSOR " + + (numOfUsedProcessors+1)); + _mainPS.println("#endif // shaper"); + _mainPS.println(); + */ + _mainPS.print("unsigned int number_of_processes[]" + + " = { "); + for (int i = 0; i < numOfUsedProcessors; i++) { + _mainPS.print(processesPerProcessor[i] + ", "); + } + _mainPS.println("};"); + _mainPS.println(); + } + + _mainPS.println("unsigned int active_processes;"); + _mainPS.println("inline void processor_init() " + + "{active_processes" + + " = number_of_processes[get_id() - 1];}"); + _mainPS.println(); + + for (Process p : pList) { + _mainPS.println("void " + "rtems_" + p.getName() + + "_init(rtems_task_argument arg);"); + } + _mainPS.println(); + } + + for (String pName : x.getProcessBasenames()) { + _mainPS.println("extern rtems_task " + pName + + "_task(rtems_task_argument argument);"); + } + _mainPS.println(); + + //declare processes and channels + for (Process process : x.getProcessList()) { + _mainPS.println("RtemsProcessWrapper *" + process.getName() + + "_wrapper;"); + } + _mainPS.println(); + + _mainPS.println("#ifdef PRINTF_TO_DEBUG"); + _mainPS.println("#undef printf"); + _mainPS.println("#define printf(...) \\"); + _mainPS.println(" do { \\"); + _mainPS.println(" char _buffer[128]; \\"); + _mainPS.println(" sprintf(_buffer, __VA_ARGS__); \\"); + _mainPS.println(" SHOW_DEBUG((int)_buffer); \\"); + _mainPS.println(" } while (0)"); + _mainPS.println("#endif"); + _mainPS.println(); + _mainPS.println("#ifdef WORKLOAD_EXTRACT"); + _mainPS.println("void callback(int code, int* arg, int size) " + + "{"); + _mainPS.println(" long unsigned int time = get_cycle1();"); + _mainPS.println(" Thread_Control *x = " + + "(Thread_Control*)arg;"); + _mainPS.println(" SHOW_DEBUG(\"log callback\");"); + _mainPS.println(" SHOW_DEBUG_INT((int)time);"); + _mainPS.println(" SHOW_DEBUG_INT(x->Object.id);"); + _mainPS.println("}"); + _mainPS.println("#endif"); + _mainPS.println(); + _mainPS.println("rtems_id timer_sem_id;"); + _mainPS.println(); + //RTEMS init task + _mainPS.println("/**"); + _mainPS.println(" *"); + _mainPS.println(" */"); + _mainPS.println("rtems_task Init(rtems_task_argument arg) {"); + + if (_ui.getRtemsBSP().equals("mparm")) { + // //////////////////// mparm ///////////////////////// + int processCount = 0; + + _mainPS.println(" rtems_id task_id;"); + _mainPS.println(" rtems_status_code status;"); + _mainPS.println(); + _mainPS.println(" status = rtems_semaphore_create(rtems_build_name('c','s','e','m'), 1, RTEMS_BINARY_SEMAPHORE |RTEMS_NO_PRIORITY_CEILING |RTEMS_LOCAL |RTEMS_PRIORITY, 0,&timer_sem_id);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] Could not create semaphore (status %d).\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + + + _mainPS.println(); + + _mainPS.println(" scratch_queue_autoinit_system();"); + _mainPS.println(); + + _mainPS.println("#ifdef WORKLOAD_EXTRACT"); + _mainPS.println(" rtems_monitor_register_callback" + + "(callback);"); + for (Channel c : x.getChannelList()) { + _mainPS.println(" SHOW_DEBUG(\"log " + + "create_channel\");"); + _mainPS.println(" SHOW_DEBUG(\"log " + c.getName() + + "\");"); + _mainPS.println(" SHOW_DEBUG_INT((int)" + + (c.getSize() * c.getTokenSize()) + ");"); + } + _mainPS.println("#endif"); + + //shaper process + _mainPS.println("#ifdef QUEUE_BUFF_SHAPER"); + _mainPS.println(" if ((get_id()-1) == shaper_PROCESSOR) " + + "{"); + _mainPS.println(" rtems_name name = rtems_build_name" + +"('s', 's', '0', ' ');"); + _mainPS.println(); + + // create pre task + _mainPS.println(" //one more init to prevent the" + + "blocking instatiation of scrach queue"); + _mainPS.println(" status = rtems_task_create("); + _mainPS.println(" name,"); + _mainPS.println(" 1,"); + _mainPS.println(" RTEMS_MINIMUM_STACK_SIZE,"); + _mainPS.println(" RTEMS_DEFAULT_MODES,"); + _mainPS.println(" RTEMS_LOCAL,"); + _mainPS.println(" &task_id, -1);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not create init shaper" + + "(status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + + // start pre init + _mainPS.println(" status = rtems_task_start(task_id, " + + "shaping_init, arg);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not start init shaping" + + " (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(" }"); + _mainPS.println("#endif // QUEUE_BUFF_SHAPER"); + _mainPS.println(); + + for (Process process : x.getProcessList()) { + _mainPS.println(" if (get_id() == " + + process.getName() + "_PROCESSOR) {"); + _mainPS.println(" processor_init();"); + _mainPS.println(" rtems_name name = " + + "rtems_build_name" + + "('p', 'i', 0x" + ++processCount + + ", ' ');"); + _mainPS.println(); + + // create pre task + _mainPS.println(" //one more init to prevent the" + + "blocking instatiation of scrach queue"); + _mainPS.println(" status = rtems_task_create("); + _mainPS.println(" name,"); + _mainPS.println(" 1,"); + _mainPS.println(" RTEMS_MINIMUM_STACK_SIZE" + + ","); + _mainPS.println(" RTEMS_DEFAULT_MODES,"); + _mainPS.println(" RTEMS_LOCAL,"); + _mainPS.println(" &task_id, -1);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) " + + "{"); + _mainPS.println(" printf(\"[init ] " + + "Could not create init task[" + + process.getName() + "] " + +"(status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + + // start pre init + _mainPS.println(" status = rtems_task_start(task_id, " + + "rtems_" + process.getName() + + "_init, arg);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not start init task [ " + + process.getName() + + "] (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + /* + _mainPS.println(" rtems_" + process.getName() + + "_init(arg);"); + _mainPS.println(" rtems_task_delete(RTEMS_SELF);"); + */ + _mainPS.println(" }"); + _mainPS.println(); + } + + // shaper process + _mainPS.println("#ifdef QUEUE_BUFF_SHAPER"); + _mainPS.println(" if ((get_id()-1) == shaper_PROCESSOR) " + + "{"); + _mainPS.println(" rtems_name name = rtems_build_name" + +"('s', 's', '0', ' ');"); + _mainPS.println(); + + // create pre task + _mainPS.println(" //one more init to prevent the" + + "blocking instatiation of scrach queue"); + _mainPS.println(" status = rtems_task_create("); + _mainPS.println(" name,"); + _mainPS.println(" 128,"); + _mainPS.println(" RTEMS_MINIMUM_STACK_SIZE,"); + _mainPS.println(" RTEMS_DEFAULT_MODES,"); + _mainPS.println(" RTEMS_LOCAL,"); + _mainPS.println(" &task_id, -1);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not create init shaper" + +"(status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + + // start pre init + _mainPS.println(" status = rtems_task_start(task_id, " + + "shaping_init, arg);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not start init shaping" + + " (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(" }"); + _mainPS.println("#endif // QUEUE_BUFF_SHAPER"); + _mainPS.println(); + + _mainPS.println("if (number_of_processes[get_id() - 1]" + + " == 0) {"); + _mainPS.println(" printf(\"No process on processor " + + "%d.\", get_id());"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println("}"); + _mainPS.println(); + + _mainPS.println(" rtems_task_delete(RTEMS_SELF);"); + _mainPS.println("}"); + _mainPS.println(); + // end of init + + processCount = 0; + for (Process process : x.getProcessList()) { + String processName = process.getName(); + _mainPS.println("void rtems_" + process.getName() + + "_init(rtems_task_argument arg) {"); + _mainPS.println(" rtems_id task_id;"); + _mainPS.println(" rtems_status_code status;"); + + + _mainPS.println(" " + process.getName() + + "_wrapper = " + + "(RtemsProcessWrapper *)malloc" + + "(sizeof(RtemsProcessWrapper));"); + + int numOfOutports = process.getNumOfOutports(); + int numOfInports = process.getNumOfInports(); + if (numOfOutports > 0) { + _mainPS.println(" int* " + + process.getName() + + "_out_port_id = (int *)malloc(" + + numOfOutports + + " * sizeof(int));"); + _mainPS.println(" SCRATCH_QUEUE_PRODUCER **" + + process.getName() + + "_out_queue_id = " + + "(SCRATCH_QUEUE_PRODUCER **)" + + "malloc(" + + numOfOutports + + "*sizeof(SCRATCH_QUEUE_PRODUCER));"); + } + + if (numOfInports > 0) { + _mainPS.println(" int *" + + process.getName() + + "_in_port_id = (int *)malloc(" + + numOfInports + + " * sizeof(int));"); + _mainPS.println(" SCRATCH_QUEUE_CONSUMER **" + + process.getName() + + "_in_queue_id = " + + "(SCRATCH_QUEUE_CONSUMER **)" + + "malloc(" + + numOfOutports + + "*sizeof(SCRATCH_QUEUE_CONSUMER));"); + } + _mainPS.println(); + + // create task + _mainPS.println(" status = rtems_task_create("); + _mainPS.println(" " + ++processCount + ","); + _mainPS.println(" 1,"); + _mainPS.println(" RTEMS_MINIMUM_STACK_SIZE,"); + _mainPS.println(" RTEMS_DEFAULT_MODES,"); + _mainPS.println(" RTEMS_LOCAL,"); + _mainPS.println(" &task_id, -1);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not create task[" + + processName + "] " + +"(status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + + _mainPS.println("#ifdef WORKLOAD_EXTRACT"); + _mainPS.println(" SHOW_DEBUG(\"log create_task\");"); + _mainPS.println(" SHOW_DEBUG_INT(task_id);"); + _mainPS.println(" SHOW_DEBUG(\"log " + processName + "\");"); + for (Port port : process.getPortList()) { + Channel c = (Channel) port.getPeerResource(); + if (port.isInPort()) { + _mainPS.println(" SHOW_DEBUG(\"log in_port\");"); + _mainPS.println(" SHOW_DEBUG_INT((int)" + _portMap.get(port) + ");"); + _mainPS.println(" SHOW_DEBUG(\"log " + c.getName() + "\");"); + } + if (port.isOutPort()) { + _mainPS.println(" SHOW_DEBUG(\"log out_port\");"); + _mainPS.println(" SHOW_DEBUG_INT((int)" + _portMap.get(port) + ");"); + _mainPS.println(" SHOW_DEBUG(\"log " + c.getName() + "\");"); + } + } + _mainPS.println("#endif"); + _mainPS.println(); + + // connect ports to channels + HashMap channel_map = + new HashMap(); + int channelCount = 1; + for (Channel c : x.getChannelList()) { + channel_map.put(c, channelCount++); + } + _mainPS.println(); + + // fill the wrapper, instantiate queues + int i = 0, j = 0; + for (Port port : process.getPortList()) { + Channel c = (Channel)(port.getPeerResource()); + if (port.isInPort()) { + _mainPS.println(" " + processName + "_in_port_id[" + + i + "] = " + + _portMap.get(port) + ";"); + _mainPS.println(" " + processName + + "_in_queue_id[" + + i + "] = "); + _mainPS.println("#if defined (QUEUE_BUFF_IN_PRODUCER) || (QUEUE_BUFF_IN_PRODUCER_DMA)"); + _mainPS.println(" scratch_queue_autoinit_consumer(" + + channel_map.get(c) + + ", true);"); + _mainPS.println("#elif defined (QUEUE_BUFF_IN_CONSUMER) || (QUEUE_BUFF_IN_CONSUMER_DMA)"); + _mainPS.println(" scratch_queue_autoinit_consumer(" + + c.getOrigin().getName() + + "_PROCESSOR, " + + channel_map.get(c) + ", " + + c.getSize() + ", " + + c.getTokenSize() + + ");"); + _mainPS.println("#elif defined (QUEUE_BUFF_IN_SHARDMEM)"); + _mainPS.println(" scratch_queue_autoinit_consumer(" + + channel_map.get(c) + + ", true);"); + _mainPS.println("#elif defined (QUEUE_BUFF_SHAPER)"); + _mainPS.println(" scratch_queue_autoinit_consumer(" + + channel_map.get(c) + + ");"); + _mainPS.println("#endif"); + + + i++; + } else if (port.isOutPort()) { + _mainPS.println(" " + processName+"_out_port_id[" + + j + "] = " + + _portMap.get(port) + ";"); + _mainPS.println(" " + processName + + "_out_queue_id[" + + j + "] = "); + + _mainPS.println("#if defined (QUEUE_BUFF_IN_PRODUCER) || (QUEUE_BUFF_IN_PRODUCER_DMA)"); + _mainPS.println(" scratch_queue_autoinit_producer(" + + c.getTarget().getName() + + "_PROCESSOR," + + channel_map.get(c) + ", " + + c.getSize() + ", " + + c.getTokenSize() + + ", 0);"); + _mainPS.println("#elif defined (QUEUE_BUFF_IN_CONSUMER) || (QUEUE_BUFF_IN_CONSUMER_DMA)"); + _mainPS.println(" scratch_queue_autoinit_producer(" + + channel_map.get(c) + + ", true);"); + _mainPS.println("#elif defined (QUEUE_BUFF_IN_SHARDMEM)"); + _mainPS.println(" scratch_queue_autoinit_producer(" + + c.getTarget().getName() + + "_PROCESSOR," + + channel_map.get(c) + ", " + + c.getSize() + ", " + + c.getTokenSize() + + ", 1);"); + _mainPS.println("#elif defined (QUEUE_BUFF_SHAPER)"); + _mainPS.println(" scratch_queue_autoinit_producer(" + + c.getTarget().getName() + + "_PROCESSOR," + + channel_map.get(c) + ", " + + c.getSize() + ", " + + c.getTokenSize() + + ");"); + _mainPS.println("#endif"); + + j++; + } + } + + if (numOfInports > 0) { + _mainPS.println(" " + processName + + "_wrapper->in_port_id = " + + processName + + "_in_port_id;"); + _mainPS.println(" " + processName + + "_wrapper->in_queue_id = " + + processName + + "_in_queue_id;"); + _mainPS.println(" " + processName + + "_wrapper->number_of_in_ports = " + + numOfInports + ";"); + } + + if (numOfOutports > 0) { + _mainPS.println(" " + processName + + "_wrapper->out_port_id = " + + processName + + "_out_port_id;"); + _mainPS.println(" " + processName + + "_wrapper->out_queue_id = " + + processName + + "_out_queue_id;"); + _mainPS.println(" " + processName + + "_wrapper->number_of_out_ports = " + + numOfOutports + ";"); + } + + _mainPS.println(" " + processName + + "_wrapper->is_detached = 0;"); + + int priority = 127; + if (mapping != null) { + Schedule s = mapping.getScheduleByResource( + process.getProcessor().getName()); + if (s != null && s.getSchedPolicy() == + SchedulingPolicy.FIXEDPRIORITY) { + ScheduleEntry e = s.getScheduleEntry( + process.getName()); + if (e != null) { + String value = e.getCfgValue("priority"); + if (value != null) { + priority = Integer.parseInt(value); + } + } + } + } + _mainPS.println(" " + processName + + "_wrapper->priority = " + priority + ";"); + + _mainPS.println(" " + processName + + "_wrapper->name = (char *)malloc((" + + processName.length() + + " + 1) * sizeof(char));"); + _mainPS.println(" strcpy(" + processName + + "_wrapper->name, " + "\"" + + processName + "\");"); + _mainPS.println(); + + _mainPS.println(" status = rtems_task_start(" + + "task_id, " + process.getBasename() + + "_task, " + "(rtems_task_argument)" + + process.getName() + "_wrapper);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) " + + "{"); + _mainPS.println(" printf(\"[init ] " + + "Could not start " + process.getName() + + " (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(" rtems_task_delete(RTEMS_SELF);"); + _mainPS.println("}"); + _mainPS.println(); + _mainPS.println(); + } + } else if (_ui.getRtemsBSP().equals("pc386")) { + // //////////////////// pc386 //////////////////////// + _mainPS.println(" int j;"); + _mainPS.println(" rtems_id task_id[" + + + (x.getProcessList().size() + 1) + "];"); + _mainPS.println(" rtems_status_code status;"); + _mainPS.println(); + + //initialize process-specific information + for (Process process : x.getProcessList()) { + int numberOfPorts = x.getProcess(process.getName()) + .getPortList().size(); + _mainPS.println(" " + process.getName() + + "_wrapper = " + + "malloc(sizeof(RtemsProcessWrapper));"); + _mainPS.println(" int *" + + process.getName() + "_port_id = malloc(" + + numberOfPorts + " * sizeof(int));"); + _mainPS.println(" int *" + + process.getName() + + "_port_queue_id = malloc(" + + numberOfPorts + " * sizeof(int));"); + _mainPS.println(); + } + + //create a task for each process + _mainPS.println(" for (j = 0; j < " + + (x.getProcessList().size()+1) + "; j++) {"); + _mainPS.println(" status = rtems_task_create("); + _mainPS.println(" j + 1,"); + _mainPS.println(" 128,"); + _mainPS.println(" RTEMS_MINIMUM_STACK_SIZE,"); + _mainPS.println(" RTEMS_DEFAULT_MODES,"); + _mainPS.println(" RTEMS_DEFAULT_ATTRIBUTES,"); + _mainPS.println(" &(task_id[j]));"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] " + + "Could not create task[%d] (status %d).\\n\", " + + "(int)j, status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(" }"); + _mainPS.println(); + + //create a message queue for each channel + int j = 0; + for (Channel channel : x.getChannelList()) { + _mainPS.println(" status = " + + "rtems_message_queue_create("); + _mainPS.println(" " + (j + 1) + ","); + _mainPS.println(" " + channel.getSize() + + ","); + _mainPS.println(" 1,"); + _mainPS.println(" RTEMS_DEFAULT_ATTR" + + "IBUTES,"); + _mainPS.println(" &queue_id[" + j + "]);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) " + + "{"); + _mainPS.println(" printf(\"[init ] " + + "Could not create queue[" + j + + "] (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + j++; + } + + //connect ports to channels + HashMap channel_map = + new HashMap(); + j = 0; + for (Channel c : x.getChannelList()) { + channel_map.put(c, j++); + } + + for (Process process : x.getProcessList()) { + String processName = process.getName(); + int i = 0; + for (Port port : process.getPortList()) { + Channel c = (Channel)(port.getPeerResource()); + + _mainPS.println(" " + processName + "_port_id[" + + i + "] = " + + _portMap.get(port) + ";"); + _mainPS.println(" " + processName + + "_port_queue_id[" + i + "] = queue_id[" + + channel_map.get(c) + "];"); + i++; + } + _mainPS.println(" " + processName + + "_wrapper->port_id = " + processName + + "_port_id;"); + _mainPS.println(" " + processName + + "_wrapper->port_queue_id = " + + processName + + "_port_queue_id;"); + _mainPS.println(" " + processName + + "_wrapper->number_of_ports = " + + i + ";"); + _mainPS.println(" " + processName + + "_wrapper->is_detached = 0;"); + _mainPS.println(" " + processName + + "_wrapper->name = malloc((" + + processName.length() + + " + 1) * sizeof(char));"); + _mainPS.println(" strcpy(" + processName + + "_wrapper->name, " + "\"" + + processName + "\");"); + _mainPS.println(); + } + + //start cleanup task + _mainPS.println(" printf(\"[init ] " + + "Start cleanup.\\n\");"); + _mainPS.println(" status = rtems_task_start(task_id[0], " + + "CleanupTask, 0);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) {"); + _mainPS.println(" printf(\"[init ] Could not start " + + "cleanup (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + + //start processes + j = 1; + for (Process process : x.getProcessList()) { + _mainPS.println(" printf(\"[init ] Start " + + process.getName() + ".\\n\");"); + _mainPS.println(" status = rtems_task_start(task_id[" + + j + "], " + process.getBasename() + + "_task, " + + "(rtems_task_argument)" + + process.getName() + + "_wrapper);"); + _mainPS.println(" if (status != RTEMS_SUCCESSFUL) " + + "{"); + _mainPS.println(" printf(\"[init ] " + + "Could not start " + process.getName() + + " (status %d).\\n\", status);"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println(" }"); + _mainPS.println(); + j++; + } + + _mainPS.println(" printf(\"[init ] Done.\\n\");"); + _mainPS.println(" rtems_task_delete(RTEMS_SELF);"); + _mainPS.println("}"); + _mainPS.println(); + + _mainPS.println("rtems_task CleanupTask(rtems_task_argument " + + "argument) {"); + _mainPS.println(" printf(\"[cleanup ] Started.\\n\");"); + _mainPS.println(" do {"); + _mainPS.println(" rtems_task_wake_after(0);"); + _mainPS.println(" } while ("); + j = 0; + for (Process process : x.getProcessList()) { + _mainPS.print(" !(" + + process.getName() + + "_wrapper->is_detached)"); + j++; + if (j < x.getProcessList().size()) { + _mainPS.println(" || "); + } else { + _mainPS.println(");"); + } + } + _mainPS.println(); + _mainPS.println(" printf(\"[cleanup ] Shutdown.\\n\");"); + _mainPS.println(" rtems_shutdown_executive(0);"); + _mainPS.println("}"); + _mainPS.println(); + } + } + catch (Exception e) { + System.out.println("RtemsModuleVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param x process that needs to be processed + */ + public void visitComponent(Process x) { + } + + /** + * + * @param x channel that needs to be processed + */ + public void visitComponent(Channel x) { + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; + protected HashMap _portMap; +} diff --git a/dol/src/dol/visitor/rtems/RtemsProcessVisitor.java b/dol/src/dol/visitor/rtems/RtemsProcessVisitor.java new file mode 100644 index 0000000..27b02a1 --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsProcessVisitor.java @@ -0,0 +1,168 @@ +/* $Id: RtemsProcessVisitor.java 213 2010-10-20 09:40:59Z khuang $ */ +package dol.visitor.rtems; + +import java.io.File; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Configuration; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.Copier; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a wrapper class for a process. + */ +public class RtemsProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public RtemsProcessVisitor(String dir, + HashMap portMap, + HashMap sinkMap) { + _dir = dir; + _portMap = portMap; + _sinkMap = sinkMap; + } + + /** + * + * @param x process network that needs to be processed + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } + catch (Exception e) { + System.out.println("RtemsProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Visit process. + * + * @param p process that needs to be processed + */ + public void visitComponent(Process p) { + try { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.c"; + File process_file = new File(filename); + File pattern_file = new File(_dir + _delimiter + + "process_wrapper_template.c"); + new Copier().copyFile(pattern_file, process_file); + + String includes = ""; + for (SourceCode code : p.getSrcList()) { + includes += "#include \"" + code.getLocality() + "\"" + + System.getProperty("line.separator"); + } + + Sed sed = new Sed(); + sed.sed(filename, "//#include \"@PROCESSNAME@.c\"", includes); + sed.sed(filename, "@PROCESSNAME@", p.getBasename()); + sed.sed(filename, "@Processname@", + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1)); + + String triggerPeriod = null; + String burstSize = null; + String burstPosition = null; + for (Configuration c : p.getCfgList()) { + if (c.getName().equals("triggerPeriod")) { + triggerPeriod = c.getValue(); + } else if (c.getName().equals("burstSize")) { + burstSize = c.getValue(); + } else if (c.getName().equals("burstPosition")) { + burstPosition = c.getValue(); + } + } + if (triggerPeriod != null) { + if (burstSize == null) { + burstSize = "0"; + } + if (burstPosition == null) { + burstPosition = "0"; + } + + String periodicTrigger = + "//limits the number of process activations\n" + + " while ((((int)get_cycle() - " + + "1511576) / " + triggerPeriod + ") " + + "< number_of_activations - (((int)get_cycle()" + // + " - 1511576) > " + burstPosition + " ? " + + " ) > " + burstPosition + " ? " + + burstSize + " : 0)) {\n" + + " rtems_task_set_priority(" + + "RTEMS_SELF, 127, &old_priority);\n" + + " rtems_task_wake_after(" + + "RTEMS_YIELD_PROCESSOR);\n" + + " }\n" + + " rtems_task_set_priority(RTEMS_SELF, " + + "wrapper->priority, &old_priority);\n"; + + sed.sed(filename, + "//placeholder for periodic trigger", + periodicTrigger); + } + + if (!p.hasOutPorts()) { + sed.sed(filename, "@ENDING_SHAPER@", + "ss_sem_signal(get_sem_terminate(" + + _sinkMap.get(p) + "));"); + } else { + sed.sed(filename, "@ENDING_SHAPER@", ""); + } + + for(SourceCode sourceCode : p.getSrcList()) { + filename = _dir + _delimiter + + sourceCode.getLocality().replaceAll( + "(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + sed.sed(filename, "", "\"dol.h\""); + + for (Port port : p.getPortList()) { + Integer portId = _portMap.get(port); + if (!port.getBasename().equals(port.getName())) { + for (Port port2 : p.getPortList()) { + if (port2.getBasename().equals(port.getBasename())) { + if (_portMap.get(port2) < _portMap.get(port)) { + portId = _portMap.get(port2); + } + } + } + } + sed.sed(filename, "(#define[ ]+PORT_\\w*[ ]+)\"?" + + port.getBasename() + "\"?", + "$1 " + portId); + } + } + } + catch (Exception e) { + System.out.println("RtemsProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected HashMap _portMap; + protected HashMap _sinkMap; +} diff --git a/dol/src/dol/visitor/rtems/RtemsPropertiesVisitor.java b/dol/src/dol/visitor/rtems/RtemsPropertiesVisitor.java new file mode 100644 index 0000000..71d9afd --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsPropertiesVisitor.java @@ -0,0 +1,93 @@ +/* $Id: RtemsPropertiesVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.rtems; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import dol.datamodel.architecture.Architecture; +import dol.datamodel.architecture.Processor; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.parser.xml.archischema.ArchiXmlParser; +import dol.parser.xml.mapschema.MapXmlParser; +import dol.visitor.PNVisitor; + +public class RtemsPropertiesVisitor extends PNVisitor { + + protected String _dir = null; + + public RtemsPropertiesVisitor(String dir) { + _dir = dir; + } + + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "properties"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println(getProperties(x)); + } catch (Exception e) { + System.out.println("RtemsPropertiesVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + public String getProperties(ProcessNetwork x) { + String file = ""; + String newline = System.getProperty("line.separator"); + file += "" + newline; + + int maxProcessorIndex; + String processorList = null; + String processList = null; + + if (_ui.getMappingFileName() == null) { + maxProcessorIndex = x.getProcessList().size() - 1; + } else { + ArchiXmlParser architectureParser = + new ArchiXmlParser(); + Architecture architecture = architectureParser.doParse( + _ui.getPlatformFileName()); + + MapXmlParser mappingParser = + new MapXmlParser(x, architecture); + Mapping mapping = mappingParser.doParse( + _ui.getMappingFileName()); + + maxProcessorIndex = 0; + for (Processor p : mapping.getProcessorList()) { + if (p.getProcessList().size() > 0) { + maxProcessorIndex = + Math.max(p.getIteratorIndices().elementAt(0), + maxProcessorIndex); + } + } + //maxProcessorIndex = mapping.getProcessorList().size() - 1; + } + + processorList = "1"; + for (int i = 1; i <= maxProcessorIndex; i++) { + processorList += "," + (i + 1); + } + + for (Process p : x.getProcessList()) { + if (processList == null) { + processList = p.getName(); + } else { + processList += "," + p.getName(); + } + } + + + file += "processors=" + (maxProcessorIndex + 1) + newline; + file += "processorList=" + processorList + newline; + file += "processList=" + processList + newline; + + return file; + } +} diff --git a/dol/src/dol/visitor/rtems/RtemsShaperVisitor.java b/dol/src/dol/visitor/rtems/RtemsShaperVisitor.java new file mode 100644 index 0000000..cd50166 --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsShaperVisitor.java @@ -0,0 +1,50 @@ +/* $Id: RtemsShaperVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.rtems; + +import java.util.HashMap; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a wrapper class for the traffic shaper. + */ +public class RtemsShaperVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public RtemsShaperVisitor(String dir, HashMap sinkMap) { + _dir = dir; + _sinkMap = sinkMap; + } + + /** + * + * @param x process network that needs to be processed + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "traffic_shaping.h"; + + Sed sed = new Sed(); + String tmp = "NUMBER_OF_QUEUES " + x.getChannelList().size(); + sed.sed(filename, "@NUMBER_OF_QUEUES@", tmp); + tmp = "NUMBER_OF_SINKS " + _sinkMap.size(); + sed.sed(filename, "@NUMBER_OF_SINKS@", tmp); + } + catch (Exception e) { + System.out.println("RtemsProcessVisitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected HashMap _sinkMap; +} diff --git a/dol/src/dol/visitor/rtems/RtemsVisitor.java b/dol/src/dol/visitor/rtems/RtemsVisitor.java new file mode 100644 index 0000000..82b20ef --- /dev/null +++ b/dol/src/dol/visitor/rtems/RtemsVisitor.java @@ -0,0 +1,179 @@ +/* $Id: RtemsVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.rtems; + +import java.io.File; +import java.util.HashMap; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a RTEMS package. + */ +public class RtemsVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param packageName name of the Rtems directory + */ + public RtemsVisitor(String packageName) { + _packageName = packageName; + } + + /** + * Visit process network. + * + * @param pn process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork pn) { + try { + File dir = new File(_packageName); + dir.mkdirs(); + + //copy library files + File source = new File(_ui.getMySystemCLib(). + replaceAll("systemC", "rtems")); + new Copier().copy(source, dir); + + //copy process source code + source = new File(_srcDirName); + new Copier().copy(source, dir); + + createPortMap(pn); + createSinkMap(pn); + pn.accept(new RtemsMakefileVisitor(_packageName)); + pn.accept(new RtemsProcessVisitor(_packageName,_portMap,_sinkMap)); + pn.accept(new RtemsModuleVisitor(_packageName, _portMap)); + pn.accept(new RtemsShaperVisitor(_packageName, _sinkMap)); + pn.accept(new RtemsPropertiesVisitor(_packageName)); + } + catch (Exception e) { + System.out.println("RtemsVisitor: exception occured: " + + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create a hashmap which maps each port of the given process network + * to an integer. For each process, ports are numbered with integers + * starting from 0. + * + * @param pn process network for which the map should be generated + */ + protected void createPortMap(ProcessNetwork pn) { + _portMap = new HashMap(); + + for (Process process : pn.getProcessList()) { + int portCount = 0; + Vector portList = process.getPortList(); + Vector portNameList = new Vector(); + portNameList.clear(); + HashMap portMap = + new HashMap(); + portMap.clear(); + + for (int i = 0; i < portList.size(); i++) { + //treat single ports differently than iterated ports + String portName = portList.elementAt(i).getName(); + String baseName = portList.elementAt(i).getBasename(); + + if (portName.equals(baseName)) { + portNameList.add(portName); + portMap.put(portName, portCount++); + } else { + String range_indices = portList.elementAt(i).getRange(); + Vector range_indices_values = getIndex(range_indices, ";"); + + String port_indices = portName; + port_indices.replaceAll(baseName, ""); + Vector port_indices_values = getIndex(port_indices, "_"); + + if (!portNameList.contains(baseName)) { + portNameList.add(baseName); + portMap.put(baseName, portCount); + + int size = 1; + for (int j = 0; j < range_indices_values.size(); j++) { + size *= range_indices_values.elementAt(j); + } + portCount += size; + } + + int portId = portMap.get(baseName); + for (int j = 0; j < port_indices_values.size(); j++) { + int weight = 1; + for (int k = j + 1; k < range_indices_values.size(); k++) { + weight *= range_indices_values.elementAt(k); + } + portId += port_indices_values.elementAt(j) * weight; + } + portMap.put(portName, portId); + } + } + + for (int i = 0; i < portList.size(); i++) { + _portMap.put(portList.elementAt(i), + portMap.get(portList.elementAt(i).getName())); + } + } + } + + + /** + * Create a hashmap which maps each sink of the given process network + * to an integer. For each process, sinks are numbered with integers + * starting from 1. + * + * @param pn process network for which the map should be generated + */ + protected void createSinkMap(ProcessNetwork pn) { + _sinkMap = new HashMap(); + int i = 1; + + for (Process process : pn.getProcessList()) { + if (!process.hasOutPorts()) { + _sinkMap.put(process, i); + i++; + } + } + } + + /** + * Gets vector of indices of a string, where the index must be + * separated by the specified separator. + * examples: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param range string to parse + * @param separator delimiter of indices + * @return vector of indices + */ + protected Vector getIndex(String range, String separator) { + Vector indices = new Vector(); + String[] subranges = range.split(separator); + for (int i = 0; i < subranges.length; i++) { + try { + int value = Integer.valueOf(subranges[i]); + indices.add(value); + } catch (Exception e) { + continue; + } + } + return indices; + } + + protected HashMap _portMap; + protected HashMap _sinkMap; + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; +} diff --git a/dol/src/dol/visitor/rtems/lib/README b/dol/src/dol/visitor/rtems/lib/README new file mode 100644 index 0000000..e5d0dc0 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/README @@ -0,0 +1,80 @@ +How-to for running code in MPARM environment: + +One-to-one mapping: +- Generate the code: ant -f runexample -Dnumber=? mparm +- Copy the generated dir 'systemc' to /MPARM/apps +- Copy the library 'queue_lib' into dir 'systemc' +- Compile the code: make +- Link the binary: ln -sf o-optimize/app.exe TargetMem_.mem + ? depends on how many processors +- Run: $SWARMDIR/bin/mpsim.x -c --intc=i -C -S -D + ? is number of processors + + +Multi-to-one mapping: +- Modify main.c + - number_of_processes: each field is number of processes per processor + - xxx_PROCESSOR: set to a same number if mapping to common processor + - processor_init(): leave one for each processor (not necessary) +- system.c + - macro CONFIGURE_MAXIMUM_TASKS should be two times larger than the + maximum number of processes mapped to one processor. +- MAXQUEUE + - where should we put this macro ? +- If using the dol mapping specification, the processor 1 should be reserved + in the case of enabling macro QUEUE_BUFF_SHAPER ! + +MPARM queue_lib spec: +- Always use interrupt +- Always use memcpy +- DMA always enables, the switch is in the Makefile +- Token size is always 32 bits +- Default Queue size is 4, can be redefined + + +Segment wide Calibration: +- Modify Makefile: + - enable macro PERFORMANCE_EXTRACT (default disable) + - uncomment line: + CXXSRCS += lib/xmlParser.cpp lib/Performance_Extraction.cpp +- main.c: Map all processes into one processor +- Run: $SWARMDIR/bin/mpsim.x -c 1 --intc=i -C -S -D + + +Communication exploration: +- Modify Makefile: + - enable one of below macro exclusively: + - QUEUE_BUFF_IN_PRODUCER (default) + - QUEUE_BUFF_IN_PRODUCER_DMA + - QUEUE_BUFF_IN_CONSUMER + - QUEUE_BUFF_IN_CONSUMER_DMA + - QUEUE_BUFF_SHAPER + +Running mpeg2 decoder on MPARM: +- copy dol.h from other case studies +- add macro _DOL_ETHZ_GEN_ to Makefile +- change queue size to 4096 in system.h +- call mpsim.x with option --s-size=19 (scratchpad size=1Mbyts) + +Running mjpeg-2000 decoder on MPARM: +- jpeg.h + - disable macro VIEWER + - disable macros VERBOSE and INFO +- scratch_queue.h + - macro MAXQUEUE > 24, if mapping all to one processor +- system.h + - macro CONFIGURE_MAXIMUM_TASKS > 9 + + +Tricks: +- CONFIGURE_MAXIMUM_TASKS in system.h +- MAXQUEUE in scratch_queue.h +- Token size should be multiple of 4 bytes +- W/R operations should be the same size, otherwise token size should be + gcd(Write_token, Read_token). + +BUGS & TODO: +- free() in wrapper not correct +- Iterated port not tested yet +- Shaper not finished! +- Timeslice works? (Yes) diff --git a/dol/src/dol/visitor/rtems/lib/appsupport.c b/dol/src/dol/visitor/rtems/lib/appsupport.c new file mode 100644 index 0000000..7ced4a5 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/appsupport.c @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2003 DEIS - Universita' di Bologna +// +// name appsupport.c +// author DEIS - Universita' di Bologna +// Davide Bertozzi - dbertozzi@deis.unibo.it +// Mirko Loghi - mloghi@deis.unibo.it +// Federico Angiolini - fangiolini@deis.unibo.it +// Francesco Poletti - fpoletti@deis.unibo.it +// portions by Massimo Scardamaglia - mascard@vizzavi.it +// info Provides support for testbench compilation +// +/////////////////////////////////////////////////////////////////////////////// +#include + +#include "appsupport.h" + +volatile char *time_low_ptr = (char *)(SIMSUPPORT_BASE + GET_TIME_ADDRESS_LO); +volatile char *time_high_ptr = (char *)(SIMSUPPORT_BASE + GET_TIME_ADDRESS_HI); +volatile char *time_stop_ptr = (char *)(SIMSUPPORT_BASE + STOP_TIME_ADDRESS); +volatile char *time_rel_ptr = (char *)(SIMSUPPORT_BASE + RELEASE_TIME_ADDRESS); + +volatile char *cycle_low_ptr = (char *)(SIMSUPPORT_BASE + GET_CYCLE_ADDRESS_LO); +volatile char *cycle_high_ptr = (char *)(SIMSUPPORT_BASE + GET_CYCLE_ADDRESS_HI); +volatile char *cycle_stop_ptr = (char *)(SIMSUPPORT_BASE + STOP_CYCLE_ADDRESS); +volatile char *cycle_rel_ptr = (char *)(SIMSUPPORT_BASE + RELEASE_CYCLE_ADDRESS); + + +/////////////////////////////////////////////////////////////////////////////// +// pr - Allows printing debug info even without support from an OS. See +// user_swi.cpp for printable messages, or to create your own + +void pr(int proc, int msg_num, int num_arg) +{ +#ifndef __OPTIMIZE__ + __asm ("ldr r1, %0" : : "g" (proc) : "r1" ); + __asm ("ldr r2, %0" : : "g" (msg_num) : "r2" ); + __asm ("ldr r3, %0" : : "g" (num_arg) : "r3" ); + __asm ("swi " SWI_PRINTstr); +#else + __asm ("mov r1, %0" : : "g" (proc) : "r1" ); + __asm ("mov r2, %0" : : "g" (msg_num) : "r2" ); + __asm ("mov r3, %0" : : "g" (num_arg) : "r3" ); + __asm ("swi " SWI_PRINTstr); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +// get_proc_id - Allows getting the processor's ID (from 1 onwards) +unsigned int get_proc_id() +{ + char *ptr = (char *)(SIMSUPPORT_BASE + GET_CPU_ID_ADDRESS); + return (*(unsigned long int *)ptr); +} + +/////////////////////////////////////////////////////////////////////////////// +// get_proc_num - Allows getting the number of processors in the platform +unsigned int get_proc_num() +{ + char *ptr = (char *)(SIMSUPPORT_BASE + GET_CPU_CNT_ADDRESS); + return (*(unsigned long int *)ptr); +} + +// --------------------------- +// Frequency scaling functions +// --------------------------- +volatile unsigned int *freqdevice = (unsigned int *)FREQ_BASE; + +/////////////////////////////////////////////////////////////////////////////// +// scale_this_core_frequency - Scales the frequency of the core on which +// the application is running +void scale_this_core_frequency(unsigned short int divider) +{ + freqdevice[get_proc_id() - 1] = divider; +} +/////////////////////////////////////////////////////////////////////////////// +// scale_device_frequency - Scales the frequency of any device in the system +void scale_device_frequency(unsigned short int divider, int ID) +{ + freqdevice[ID] = divider; +} +/////////////////////////////////////////////////////////////////////////////// +// get_this_core_frequency - Gets the frequency divider of the core on which +// the application is running +unsigned short int get_this_core_frequency() +{ + return (freqdevice[get_proc_id() - 1]); +} +/////////////////////////////////////////////////////////////////////////////// +// get_device_frequency - Gets the frequency divider of any device in the system +unsigned short int get_device_frequency(int ID) +{ + return (freqdevice[ID]); +} + +/////////////////////////////////////////////////////////////////////////////// +// get_time - Allows getting the current simulation time +unsigned long long int get_time() +{ + unsigned long long int time; + + *time_stop_ptr = 1; + time = (((unsigned long long int)(*(unsigned long int *)time_high_ptr)) << 32) + *(unsigned long int *)time_low_ptr; + *time_rel_ptr = 1; + + return (time); +} + +/////////////////////////////////////////////////////////////////////////////// +// get_cycle - Allows getting the current simulation cycle +unsigned long long int get_cycle1() +{ + unsigned long long int cycle; + + *cycle_stop_ptr = 1; + cycle = (((unsigned long long int)(*(unsigned long int *)cycle_high_ptr)) << 32) + *(unsigned long int *)cycle_low_ptr; + *cycle_rel_ptr = 1; + + return (cycle); +} + +extern rtems_id timer_sem_id; +unsigned long long int get_cycle() +{ + unsigned long long int cycle; + rtems_status_code rtems_status; + + rtems_status = rtems_semaphore_obtain(timer_sem_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + *cycle_stop_ptr = 1; + cycle = (((unsigned long long int)(*(unsigned long int *)cycle_high_ptr)) + << 32) + *(unsigned long int *)cycle_low_ptr; + *cycle_rel_ptr = 1; + rtems_status = rtems_semaphore_release(timer_sem_id); + + return (cycle); +} diff --git a/dol/src/dol/visitor/rtems/lib/appsupport.h b/dol/src/dol/visitor/rtems/lib/appsupport.h new file mode 100644 index 0000000..6e50558 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/appsupport.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2003 DEIS - Universita' di Bologna +// +// name appsupport.h +// author DEIS - Universita' di Bologna +// Davide Bertozzi - dbertozzi@deis.unibo.it +// Mirko Loghi - mloghi@deis.unibo.it +// Federico Angiolini - fangiolini@deis.unibo.it +// Francesco Poletti - fpoletti@deis.unibo.it +// portions by Massimo Scardamaglia - mascard@vizzavi.it +// info Provides support for testbench compilation +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __APPSUPPORT_H__ +#define __APPSUPPORT_H__ + +#include "swi_calls.h" +#include "config.h" +#include "sim_support_flags.h" + +#define start_metric() __asm ("swi " SWI_METRIC_STARTstr) +#define stop_metric() __asm ("swi " SWI_METRIC_STOPstr) +#define dump_metric() __asm ("swi " SWI_METRIC_DUMPstr) +#define clear_metric() __asm ("swi " SWI_METRIC_CLEARstr) +#define stop_simulation() __asm ("swi " SWI_EXITstr) +void pr(int proc, int msg_num, int num_arg); +unsigned int get_proc_id(); + +#undef get_id +#define get_id() get_proc_id() + +unsigned int get_proc_num(); + +// --------------------------- +// Frequency scaling functions +// --------------------------- +void scale_this_core_frequency(unsigned short int divider); +void scale_device_frequency(unsigned short int divider, int ID); +unsigned short int get_this_core_frequency(); +unsigned short int get_device_frequency(int ID); + +#define RTEMS_TRACE_MAIN_APP + +#ifdef RTEMS_TRACE_MAIN_APP +//It print out the processor id, the parameter "a" and the actual clock cycles +//Eg. Processor 0 - 0x3e740 start_cycle:353089 +#define SHOW_TIME_START(a) pr(0x11111, 0x10020, (int)a) +//Eg. Processor 0 - 0x3e740 stop_cycle:353143 +#define SHOW_TIME_STOP(a) pr(0x11111, 0x10030, (int)a) +//It print out the string a +#define SHOW_DEBUG(a) pr(0x11111, 0x10000, (int)a) +#define SHOW_DEBUG_NON(a) pr(0x11111, 0x10001, (int)a) +#define SHOW_DEBUG_NOP(a) pr(0x11111, 0x10002, (int)a) +#define SHOW_DEBUG_NON_NOP(a) pr(0x11111, 0x10003, (int)a) +//It print out the integer a +#define SHOW_DEBUG_INT(a) pr(0x11111, 0x10010, (int)a) +#define SHOW_DEBUG_INT_NON(a) pr(0x11111, 0x10011, (int)a) +#define SHOW_DEBUG_INT_NOP(a) pr(0x11111, 0x10012, (int)a) +#define SHOW_DEBUG_INT_NON_NOP(a) pr(0x11111, 0x10013, (int)a) +#else +//empty definitions +#define SHOW_TIME_START(a) +#define SHOW_TIME_STOP(a) +#define SHOW_DEBUG(a) +#define SHOW_DEBUG_NON(a) +#define SHOW_DEBUG_INT_NOP(a) +#define SHOW_DEBUG_INT_NON_NOP(a) +#define SHOW_DEBUG_INT(a) +#define SHOW_DEBUG_INT_NON(a) +#define SHOW_DEBUG_INT_NOP(a) +#define SHOW_DEBUG_INT_NON_NOP(a) +#endif + +unsigned long long int get_time(); +unsigned long long int get_cycle(); +unsigned long long int get_cycle1(); + +#endif // __APPSUPPORT_H__ diff --git a/dol/src/dol/visitor/rtems/lib/buffer_test_io.h b/dol/src/dol/visitor/rtems/lib/buffer_test_io.h new file mode 100644 index 0000000..605a608 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/buffer_test_io.h @@ -0,0 +1,117 @@ +/* + * Support for running the test output through a buffer + * + * buffer_test_io.h,v 1.1 2002/08/02 00:51:52 joel Exp + */ + +#ifndef __BUFFER_TEST_IO_h +#define __BUFFER_TEST_IO_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * Uncomment this to get buffered test output. When commented out, + * test output behaves as it always has and is printed ASAP. + */ + +/* #define TESTS_BUFFER_OUTPUT */ + +#if !defined(TESTS_BUFFER_OUTPUT) + +#define rtems_test_exit(_s) \ + do { \ + exit(_s); \ + } while (0) + +#define FLUSH_OUTPUT() \ + do { \ + fflush(stdout); \ + } while (0) + +#else /* buffer test output */ + +#define _TEST_OUTPUT_BUFFER_SIZE 2048 +extern char _test_output_buffer[_TEST_OUTPUT_BUFFER_SIZE]; +void _test_output_append(char *); +void _test_output_flush(void); + +#define rtems_test_exit(_s) \ + do { \ + _test_output_flush(); \ + exit(_s); \ + } while (0) + +#undef printf +#define printf(...) \ + do { \ + char _buffer[128]; \ + sprintf( _buffer, __VA_ARGS__); \ + _test_output_append( _buffer ); \ + } while (0) + +#undef puts +#define puts(_string) \ + do { \ + char _buffer[128]; \ + sprintf( _buffer, "%s\n", _string ); \ + _test_output_append( _buffer ); \ + } while (0) + +#undef putchar +#define putchar(_c) \ + do { \ + char _buffer[2]; \ + _buffer[0] = _c; \ + _buffer[1] = '\0'; \ + _test_output_append( _buffer ); \ + } while (0) + +/* we write to stderr when there is a pause() */ +#define FLUSH_OUTPUT() _test_output_flush() + +#if defined(TEST_INIT) || defined(CONFIGURE_INIT) + +char _test_output_buffer[_TEST_OUTPUT_BUFFER_SIZE]; +int _test_output_buffer_index = 0; + +void _test_output_append(char *_buffer) +{ + char *p; + + for ( p=_buffer ; *p ; p++ ) { + _test_output_buffer[_test_output_buffer_index++] = *p; + _test_output_buffer[_test_output_buffer_index] = '\0'; +#if 0 + if ( *p == '\n' ) { + fprintf( stderr, "BUFFER -- %s", _test_output_buffer ); + _test_output_buffer_index = 0; + _test_output_buffer[0] = '\0'; + } +#endif + if ( _test_output_buffer_index >= (_TEST_OUTPUT_BUFFER_SIZE - 80) ) + _test_output_flush(); + } +} + +#include +#include + +void _test_output_flush(void) +{ + fprintf( stderr, "%s", _test_output_buffer ); + _test_output_buffer_index = 0; + tcdrain( 2 ); +} + +#endif /* TEST_INIT */ +#endif /* TESTS_BUFFER_OUTPUT */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dol/src/dol/visitor/rtems/lib/dol.h b/dol/src/dol/visitor/rtems/lib/dol.h new file mode 100644 index 0000000..f07b246 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/dol.h @@ -0,0 +1,26 @@ +#ifndef __DOL_H__ +#define __DOL_H__ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//structure for process +struct _process; + +// +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; +} DOLProcess; + +void DOL_read(void *port, void *buf, int len, DOLProcess *process); +void DOL_write(void *port, void *buf, int len, DOLProcess *process); +void DOL_detach(DOLProcess *process); + +#endif diff --git a/dol/src/dol/visitor/rtems/lib/process_wrapper_template.c b/dol/src/dol/visitor/rtems/lib/process_wrapper_template.c new file mode 100644 index 0000000..15c262b --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/process_wrapper_template.c @@ -0,0 +1,116 @@ +#include +#include +#include + +#include "rtems_process_wrapper.h" + +#ifdef WORKLOAD_EXTRACT +#define DOL_read(port, buf, len, process) \ +{ ((RtemsProcessWrapper*)process->wptr)->end_line = __LINE__; \ + DOL_read(port, buf, len, process); \ + ((RtemsProcessWrapper*)process->wptr)->start_line = __LINE__; } + +#define DOL_write(port, buf, len, process) \ +{ ((RtemsProcessWrapper*)process->wptr)->end_line = __LINE__; \ + DOL_write(port, buf, len, process); \ + ((RtemsProcessWrapper*)process->wptr)->start_line = __LINE__; } +#endif + +#ifdef PRINTF_TO_DEBUG +#undef printf +#define printf(...) \ + do { \ + char _buffer[128]; \ + sprintf(_buffer, __VA_ARGS__); \ + SHOW_DEBUG((int)_buffer); \ + } while (0) +#endif + +//#include "@PROCESSNAME@.c" + +//DOL-specific implementation +rtems_task @PROCESSNAME@_task(rtems_task_argument argument) { + RtemsProcessWrapper* wrapper = (RtemsProcessWrapper*)argument; + DOLProcess* process; + LocalState state; + int number_of_activations = 0; + int i; + rtems_task_priority old_priority; + + process = (DOLProcess *)malloc(sizeof(DOLProcess)); + state = (LocalState)malloc(sizeof(@Processname@_State)); + process->init = @PROCESSNAME@_init; + process->fire = @PROCESSNAME@_fire; + process->local = state; + process->wptr = wrapper; + + //initialize the index array + wrapper->index = (int *)malloc(4 * sizeof(int)); + for (i = 0; i < 4; i++) { + wrapper->index[i] = getIndex(wrapper->name, "_", i); + } + + printf("Start process %s.\n", wrapper->name); + process->init(process); + + //yield process such that other processes can be initialized +#ifdef MPARM + do { + rtems_task_wake_after(RTEMS_YIELD_PROCESSOR); + } while((int)get_cycle() < 1511576); +#endif + rtems_task_set_priority(RTEMS_SELF, wrapper->priority, &old_priority); + + while (!wrapper->is_detached) { + + //placeholder for periodic trigger + +#ifdef WORKLOAD_EXTRACT + wrapper->start_line = 0; + SHOW_DEBUG("log start_fire"); + SHOW_DEBUG_INT((int)get_cycle()); +#endif + + process->fire(process); + +#ifdef WORKLOAD_EXTRACT + SHOW_DEBUG("log end_fire"); + SHOW_DEBUG_INT((int)get_cycle()); + wrapper->end_line = 0; +#endif + number_of_activations++; + } + +#ifdef MPARM_SCRATCHPAD_QUEUE + #ifdef QUEUE_BUFF_SHAPER + @ENDING_SHAPER@ + #endif +#else // pc386 + //below is not necessary if no dynamic switching + //printf("Detached @PROCESSNAME@. Cleanup... "); + if (wrapper->index) { + free(wrapper->index); + } else { + printf("Could not free memory for index of @PROCESSNAME@.\n"); + } + + if (wrapper->port_id) { + free(wrapper->port_id); + } else { + printf("Could not free memory for port_id of @PROCESSNAME@.\n"); + } + + if (wrapper->port_queue_id) { + } else { + printf("Could not free memory for port_queue_id of @PROCESSNAME@.\n"); + } +#endif // MPARM_SCRATCHPAD_QUEUE + //printf("Done.\n"); + + printf("Detach process %s.\n", wrapper->name); +#ifdef MPARM + Cleanup(); +#endif + + rtems_task_delete(RTEMS_SELF); +} diff --git a/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.c b/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.c new file mode 100644 index 0000000..387818e --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.c @@ -0,0 +1,253 @@ +#include +#include "rtems_process_wrapper.h" + +/** + * + */ +void DOL_read(void *port, void *buf, int len, DOLProcess *process) { + RtemsProcessWrapper* process_wrapper = (RtemsProcessWrapper*)process->wptr; + int i; + +#ifdef WORKLOAD_EXTRACT + SHOW_DEBUG("log start_read"); + SHOW_DEBUG_INT((int)get_cycle()); + SHOW_DEBUG_INT((int)port); + SHOW_DEBUG_INT((int)(process_wrapper->start_line)); + SHOW_DEBUG_INT((int)(process_wrapper->end_line)); + SHOW_DEBUG_INT((int)len); +#endif + +#ifdef MPARM_SCRATCHPAD_QUEUE + for (i = 0; i < process_wrapper->number_of_in_ports; i++) { + // len not taken into account yet !!!!!!! + if (process_wrapper->in_port_id[i] == (int)port) { + SCRATCH_QUEUE_CONSUMER* queue_id = + (SCRATCH_QUEUE_CONSUMER*)process_wrapper->in_queue_id[i]; + +#if defined (QUEUE_BUFF_IN_PRODUCER) + scratch_queue_read(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_PRODUCER_DMA) + scratch_queue_read_dma(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_CONSUMER) + scratch_queue_read(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_CONSUMER_DMA) + scratch_queue_read(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_SHARDMEM) + scratch_queue_read(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_SHAPER) + scratch_queue_read_2(queue_id, (char *)buf, len); +#endif + break; + } + } +#else // pc386 + for (i = 0; i < process_wrapper->number_of_ports; i++) { + if (process_wrapper->port_id[i] == (int)port) { + int queue_id = process_wrapper->port_queue_id[i]; + int j; + + //receive message byte-per-byte + for (j = 0; j < len; j++) { + size_t size = 1; + rtems_message_queue_receive( + queue_id, + buf + j, + &size, + RTEMS_DEFAULT_ATTRIBUTES, + RTEMS_NO_TIMEOUT); + } + break; + } + } +#endif // end MPARM_SCRATCHPAD_QUEUE + + //SHOW_DEBUG("read from app"); + //SHOW_DEBUG_INT(*(int *)buf); + +#ifdef WORKLOAD_EXTRACT + SHOW_DEBUG("log end_read"); + SHOW_DEBUG_INT((int)get_cycle()); + SHOW_DEBUG_INT((int)port); +#endif +} + +/** + * + */ +void DOL_write(void *port, void *buf, int len, DOLProcess *process) { + RtemsProcessWrapper* process_wrapper = (RtemsProcessWrapper*)process->wptr; + int i; + +#ifdef WORKLOAD_EXTRACT + SHOW_DEBUG("log start_write"); + SHOW_DEBUG_INT((int)get_cycle()); + SHOW_DEBUG_INT((int)port); + SHOW_DEBUG_INT((int)(process_wrapper->start_line)); + SHOW_DEBUG_INT((int)(process_wrapper->end_line)); + SHOW_DEBUG_INT((int)len); +#endif + +#if defined MPARM_SCRATCHPAD_QUEUE + for (i = 0; i < process_wrapper->number_of_out_ports; i++) { + if (process_wrapper->out_port_id[i] == (int)port) { + SCRATCH_QUEUE_PRODUCER* queue_id = + (SCRATCH_QUEUE_PRODUCER*)process_wrapper->out_queue_id[i]; + + +#if defined (QUEUE_BUFF_IN_PRODUCER) || (QUEUE_BUFF_IN_CONSUMER) + scratch_queue_write(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_PRODUCER_DMA) || (QUEUE_BUFF_IN_CONSUMER_DMA) + scratch_queue_write_dma(queue_id, (char *)buf, len); +#elif defined (QUEUE_BUFF_IN_SHARDMEM) + scratch_queue_write(queue_id, (char *)buf, len); +#elif defined(QUEUE_BUFF_SHAPER) + scratch_queue_write_2(queue_id, (char *)buf, len); // (2) shaper +#endif + + break; + } + } +#else // pc386 + for (i = 0; i < process_wrapper->number_of_ports; i++) { + if (process_wrapper->port_id[i] == (int)port) { + int queue_id = process_wrapper->port_queue_id[i]; + rtems_status_code status; + int j; + + //send message byte-per-byte + for (j = 0; j < len; j++) { + do { + status = rtems_message_queue_send(queue_id, buf + j, 1); + if (status != RTEMS_SUCCESSFUL) { + rtems_task_wake_after(0); + } + } while (status != RTEMS_SUCCESSFUL); + } + + break; + } + } +#endif // end MPARM_SCRATCHPAD_QUEUE + +#ifdef WORKLOAD_EXTRACT + SHOW_DEBUG("log end_write"); + SHOW_DEBUG_INT((int)get_cycle()); + SHOW_DEBUG_INT((int)port); +#endif +} + +/** + * + */ +void DOL_detach(DOLProcess *process) { + //printf("[%s] DOL_detach.\n", (char*)(process->local)); + ((RtemsProcessWrapper*)process->wptr)->is_detached = 1; +} + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int getIndex(const char* string, char* tokens, int indexNumber) { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + free(string_copy); + return -1; +} + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +int *createPort(int *port, int base, int number_of_indices, + int index_range_pairs, ...) { + int index[4]; + int range[4]; + int i; + int value; + + va_list marker; + va_start(marker, index_range_pairs); + + value = index_range_pairs; + for (i = 0; i < number_of_indices; i++) { + index[i] = value; + value = va_arg(marker, int); + range[i] = value; + if (i < number_of_indices - 1) { + value = va_arg(marker, int); + } + } + + *port = base; + for (i = 0; i < number_of_indices; i++) { + int j; + int weight = 1; + for (j = 1; j < number_of_indices - i; j ++) { + weight *= range[j]; + } + *port += index[i] * weight; + } + + /* + switch (number_of_indices) { + case 1: + *port = base + index[0]; + break; + case 2: + *port = base + index[0] * range[1] + + index[1]; + break; + case 3: + *port = base + index[0] * range[1] * range[2] + + index[1] * range[0] + + index[2]; + break; + case 4: + *port = base + index[0] * range[1] * range[2] * range[3] + + index[1] * range[2] * range[3] + + index[2] * range[3] + + index[3]; + break; + } + */ + + return port; +} diff --git a/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.h b/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.h new file mode 100644 index 0000000..c72022b --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/rtems_process_wrapper.h @@ -0,0 +1,106 @@ +#ifndef __RTEMS_PROCESS_WRAPPER_H__ +#define __RTEMS_PROCESS_WRAPPER_H__ + +#include +#include +#include +#include +#include +#include "dol.h" + +#ifdef MPARM_SCRATCHPAD_QUEUE +#include "scratch_queue.h" +#endif + +#ifdef MPARM +#define printf(format ...) { char stringBuffer[80]; sprintf(stringBuffer, format); SHOW_DEBUG(stringBuffer); } +#endif + +typedef struct _process_wrapper { + DOLProcess *dol_process; + char* name; + int* index; + int is_detached; + int priority; +#ifdef WORKLOAD_EXTRACT + int start_line; + int end_line; +#endif +#ifdef MPARM_SCRATCHPAD_QUEUE + int* in_port_id; + int* out_port_id; + SCRATCH_QUEUE_PRODUCER** out_queue_id; + SCRATCH_QUEUE_CONSUMER** in_queue_id; + int number_of_in_ports; + int number_of_out_ports; +#else // for pc396 + int* port_id; + int* port_queue_id; + int number_of_ports; +#endif +} RtemsProcessWrapper; + +// define queue memory in consumer scratchpad +//#define QUEUE_IN_CONSUMER + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int getIndex(const char* string, char* tokens, int indexNumber); + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +int *createPort(int *port, int base, int number_of_indices, int index_range_pairs, ...); + +//DOL macros +#define GETINDEX(dimension) \ + ((RtemsProcessWrapper*)(p->wptr))->index[dimension] + +#define CREATEPORTVAR(name) \ + int name + +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort(&port, base, number_of_indices, index_range_pairs) + +#ifdef PERFORMANCE_EXTRACT +#include "Performance_Extraction.h" +#endif + +#ifdef MPARM +extern unsigned int active_processes; +inline void Cleanup() { + register rtems_interrupt_level level; + rtems_interrupt_disable(level); + active_processes--; + + if(active_processes==0) { +#ifdef PERFORMANCE_EXTRACT + performance_extraction.write_to_xml_file("calibration"); +#endif + printf("All processes detached."); + rtems_interrupt_enable(level); + exit(0); + } + rtems_interrupt_enable(level); +} +#endif + +#endif diff --git a/dol/src/dol/visitor/rtems/lib/system.h b/dol/src/dol/visitor/rtems/lib/system.h new file mode 100644 index 0000000..147d79f --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/system.h @@ -0,0 +1,64 @@ +/* system.h + * + * This include file contains information that is included in every + * function in the test set. + * + * COPYRIGHT (c) 1989-1999. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.OARcorp.com/rtems/license.html. + * + * system.h,v 1.12 2000/06/12 15:00:11 joel Exp + */ + +#include "tmacros.h" +#include "appsupport.h" + +// Functions +rtems_task Init(rtems_task_argument argument); +rtems_task Test_task(rtems_task_argument argument); + +// Configuration information +#define CONFIGURE_TEST_NEEDS_CONSOLE_DRIVER + +#define MAXIMUM_REGIONS 2 + +#define CONFIGURE_MAXIMUM_MESSAGE_QUEUES 4 + +// Tells confdefs.h to build the init task configuration +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE +// ?????? +#define CONFIGURE_TICKS_PER_TIMESLICE 100 // ??? + +#define CONFIGURE_MAXIMUM_TASKS 32 + +// Tells confdefs.h to build the default configuration table +#ifdef TEST_INIT +#define CONFIGURE_INIT +#endif + +#define CONFIGURE_MP_APPLICATION +//These are just to allow the OS to be able to compile. +//Then this value is overwritten during the boot of the OS, +//using the call to coprocessor functions +#define CONFIGURE_MP_MAXIMUM_NODES 1 +#define NODE_NUMBER 1 + + +///////////////////////////////// +#define SCRATCHPAD_QUEUE // enable scratchpad queue + +#define DEFAULT_BUFFER_SIZE 100 //scratchpad queue size +#define TOKEN_BYTES 4096 + +#define CONFIGURE_MAXIMUM_SEMAPHORES 2 + +//#define MAXQUEUE 10 +//#define SCRATCH_SIZE_PLATFORM (1024*512) + + + +#include + diff --git a/dol/src/dol/visitor/rtems/lib/tmacros.h b/dol/src/dol/visitor/rtems/lib/tmacros.h new file mode 100644 index 0000000..76308ad --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/tmacros.h @@ -0,0 +1,247 @@ +/* tmacros.h + * + * This include file contains macros which are useful in the RTEMS + * test suites. + * + * COPYRIGHT (c) 1989-1999. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.OARcorp.com/rtems/license.html. + * + * tmacros.h,v 1.25 2002/08/02 00:51:52 joel Exp + */ + +#ifndef __TMACROS_h +#define __TMACROS_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* includes */ + +#include +#include +#include +#include +#include + +#define FOREVER 1 /* infinite loop */ + +#ifdef TEST_INIT +#define TEST_EXTERN +#else +#define TEST_EXTERN extern +#endif + +#include "buffer_test_io.h" + +/* + * Check that that the dispatch disable level is proper for the + * mode/state of the test. Normally it should be 0 when in task space. + */ + +#define check_dispatch_disable_level( _expect ) \ + do { \ + extern volatile rtems_unsigned32 _Thread_Dispatch_disable_level; \ + if ( (_expect) != -1 && _Thread_Dispatch_disable_level != (_expect) ) { \ + printf( "\n_Thread_Dispatch_disable_level is (%d) not %d\n", \ + _Thread_Dispatch_disable_level, (_expect) ); \ + FLUSH_OUTPUT(); \ + rtems_test_exit( 1 ); \ + } \ + } while ( 0 ) + +/* + * These macros properly report errors within the Classic API + */ + +#define directive_failed( _dirstat, _failmsg ) \ + fatal_directive_status( _dirstat, RTEMS_SUCCESSFUL, _failmsg ) + +#define directive_failed_with_level( _dirstat, _failmsg, _level ) \ + fatal_directive_status_with_level( \ + _dirstat, RTEMS_SUCCESSFUL, _failmsg, _level ) + +#define fatal_directive_status( _stat, _desired, _msg ) \ + fatal_directive_status_with_level( _stat, _desired, _msg, 0 ) + +#define fatal_directive_check_status_only( _stat, _desired, _msg ) \ + do { \ + if ( (_stat) != (_desired) ) { \ + printf( "\n%s FAILED -- expected (%s) got (%s)\n", \ + (_msg), rtems_status_text(_desired), rtems_status_text(_stat) ); \ + FLUSH_OUTPUT(); \ + rtems_test_exit( _stat ); \ + } \ + } while ( 0 ) + +#define fatal_directive_status_with_level( _stat, _desired, _msg, _level ) \ + do { \ + check_dispatch_disable_level( _level ); \ + fatal_directive_check_status_only( _stat, _desired, _msg ); \ + } while ( 0 ) + +/* + * These macros properly report errors from the POSIX API + */ + +#define posix_service_failed( _dirstat, _failmsg ) \ + fatal_posix_service_status( _dirstat, RTEMS_SUCCESSFUL, _failmsg ) + +#define posix_service_failed_with_level( _dirstat, _failmsg, _level ) \ + fatal_posix_service_status_with_level( \ + _dirstat, RTEMS_SUCCESSFUL, _failmsg, _level ) + +#define fatal_posix_service_status( _stat, _desired, _msg ) \ + fatal_posix_service_status_with_level( _stat, _desired, _msg, 0 ) + +#define fatal_posix_service_status_with_level( _stat, _desired, _msg, _level ) \ + do { \ + check_dispatch_disable_level( _level ); \ + if ( (_stat) != (_desired) ) { \ + printf( "\n%s FAILED -- expected (%d - %s) got (%d - %s)\n", \ + (_msg), _desired, strerror(_desired), _stat, strerror(_stat) ); \ + printf( "\n FAILED -- errno (%d - %s)\n", \ + errno, strerror(errno) ); \ + FLUSH_OUTPUT(); \ + rtems_test_exit( _stat ); \ + } \ + } while ( 0 ) + +/* + * Generic integer version of the error reporting + */ + +#define int_service_failed( _dirstat, _failmsg ) \ + fatal_int_service_status( _dirstat, RTEMS_SUCCESSFUL, _failmsg ) + +#define int_service_failed_with_level( _dirstat, _failmsg, _level ) \ + fatal_int_service_status_with_level( \ + _dirstat, RTEMS_SUCCESSFUL, _failmsg, _level ) + +#define fatal_int_service_status( _stat, _desired, _msg ) \ + fatal_int_service_status_with_level( _stat, _desired, _msg, 0 ) + +#define fatal_int_service_status_with_level( _stat, _desired, _msg, _level ) \ + do { \ + check_dispatch_disable_level( _level ); \ + if ( (_stat) != (_desired) ) { \ + printf( "\n%s FAILED -- expected (%d) got (%d)\n", \ + (_msg), (_desired), (_stat) ); \ + FLUSH_OUTPUT(); \ + rtems_test_exit( _stat ); \ + } \ + } while ( 0 ) + +/* + * Print the time: modificata per swarm da Poletti Francesco per risolvere il + * problema di stampa degli interi + */ + +#define print_time(_s1, _tb, _s2) \ + do { \ + myprint( "%s%02d:%02d:%02d %02d/%02d/%0d%s", \ + _s1, (_tb)->hour, (_tb)->minute, (_tb)->second, \ + (_tb)->month, (_tb)->day, (_tb)->year, _s2 ); \ + fflush(stdout); \ + } while ( 0 ) + + +#define sprint_time(_str, _s1, _tb, _s2) \ + do { \ + sprintf( (str), "%s%02d:%02d:%02d %02d/%02d/%04d%s", \ + _s1, (_tb)->hour, (_tb)->minute, (_tb)->second, \ + (_tb)->month, (_tb)->day, (_tb)->year, _s2 ); \ + } while ( 0 ) + +#define put_dot( _c ) \ + do { \ + putchar( _c ); \ + FLUSH_OUTPUT(); \ + } while ( 0 ) + +#define new_line puts( "" ) + +#define puts_nocr printf + +#ifdef RTEMS_TEST_NO_PAUSE +#define rtems_test_pause() \ + do { \ + printf( "\n" ); \ + FLUSH_OUTPUT(); \ + } while ( 0 ) + +#define rtems_test_pause_and_screen_number( _screen ) \ + do { \ + printf( "\n", (_screen) ); \ + FLUSH_OUTPUT(); \ + } while ( 0 ) +#else +#define rtems_test_pause() \ + do { \ + char buffer[ 80 ]; \ + printf( "" ); \ + FLUSH_OUTPUT(); \ + gets( buffer ); \ + puts( "" ); \ + } while ( 0 ) + +#define rtems_test_pause_and_screen_number( _screen ) \ + do { \ + char buffer[ 80 ]; \ + printf( "", (_screen) ); \ + FLUSH_OUTPUT(); \ + gets( buffer ); \ + puts( "" ); \ + } while ( 0 ) +#endif + +#define put_name( _name, _crlf ) \ + do { char buf[6];int i=1;\ + rtems_unsigned32 c0, c1, c2, c3; \ + \ + c0 = ((_name) >> 24) & 0xff; \ + c1 = ((_name) >> 16) & 0xff; \ + c2 = ((_name) >> 8) & 0xff; \ + c3 = (_name) & 0xff; \ + buf[0]=c0; \ + if ( c1 ) {buf[i]=(char)c1; i++;} ; \ + if ( c2 ) {buf[i]=(char)c2; i++;}; \ + if ( c3 ) {buf[i]=(char)c3; i++;}; \ + if ( (_crlf) ) {buf[i]='\n'; i++;}\ + buf[i]=0;\ + printf(buf);\ + } while (0) + +#ifndef build_time +#define build_time( TB, MON, DAY, YR, HR, MIN, SEC, TK ) \ + { (TB)->year = YR; \ + (TB)->month = MON; \ + (TB)->day = DAY; \ + (TB)->hour = HR; \ + (TB)->minute = MIN; \ + (TB)->second = SEC; \ + (TB)->ticks = TK; } +#endif + +#define task_number( tid ) \ + ( rtems_get_index( tid ) - \ + rtems_configuration_get_rtems_api_configuration()->number_of_initialization_tasks ) + +static inline rtems_unsigned32 get_ticks_per_second( void ) +{ + rtems_interval ticks_per_second; + (void) rtems_clock_get( RTEMS_CLOCK_GET_TICKS_PER_SECOND, &ticks_per_second ); + return ticks_per_second; +} + +#define TICKS_PER_SECOND get_ticks_per_second() + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dol/src/dol/visitor/rtems/lib/traffic_shaping.c b/dol/src/dol/visitor/rtems/lib/traffic_shaping.c new file mode 100644 index 0000000..d47d2f5 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/traffic_shaping.c @@ -0,0 +1,189 @@ +#include +#include + +#include "appsupport.h" +#include "rtems_process_wrapper.h" +#include "traffic_shaping.h" + +unsigned int sink_processes; +void sink_end_process(rtems_task_argument argument); +void queue_status_process(rtems_task_argument argument); +void shaping_process(rtems_task_argument argument); +rtems_task shaping_init(rtems_task_argument argument) { + rtems_id task_id; + rtems_status_code status; + QUEUE_WRAPPER *queue_wrap, *sink_wrap; + rtems_name name; + int i; + + // instatiate daemons for data transfer notices + queue_wrap = (QUEUE_WRAPPER *)malloc(NUMBER_OF_QUEUES * + sizeof(QUEUE_WRAPPER)); + for (i=0; i < NUMBER_OF_QUEUES; i++) { + queue_wrap[i].q = scratch_queue_autoinit_shaper(i+1, 0, 0, 0); + queue_wrap[i].hasData = 0; + } + + for (i=0; i< NUMBER_OF_QUEUES; i++) { + name = rtems_build_name('q', 's', i+1, 'c'); + status = rtems_task_create(name, + 127, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_LOCAL, + &task_id, -1); + if (status != RTEMS_SUCCESSFUL) { + printf("[init ] Create queue process failed (status %d).\n", + status); + rtems_shutdown_executive(0); + } + status = rtems_task_start(task_id, queue_status_process, + (rtems_task_argument)&queue_wrap[i]); + if (status != RTEMS_SUCCESSFUL) { + printf("[init ] Start queue process failed (status %d).\n", + status); + rtems_shutdown_executive(0); + } + } + + // instatiate daemons to wait for sinks finishing + sink_processes = NUMBER_OF_SINKS; + sink_wrap = (QUEUE_WRAPPER *)malloc(NUMBER_OF_SINKS * + sizeof(QUEUE_WRAPPER)); + for (i=0; iq; + +/* printf("queue:%x, traffice_shaping: " + "\n\t sem_shaper_used:%x, " + "\n\t sem_shaper_left:%x, " + "\n\t semaphore_left:%x, " + "\n\t semaphore_used:%x", + wrap->q, + queue->sem_shaper_used, + queue->sem_shaper_left, + queue->semaphore_left, + queue->semaphore_used + ); */ + + while (1) { + // always USEINTERRUPT + ss_sem_wait(queue->sem_shaper_used); + ss_sem_wait(queue->sem_shaper_left); + + wrap->hasData ++; + } +} + +void sink_end_process(rtems_task_argument argument) +{ + QUEUE_WRAPPER *queue_wrap = (QUEUE_WRAPPER *) argument; + + // wait for terminating signal from sink + ss_sem_wait(queue_wrap->sem_terminate); + + sink_processes--; + if (sink_processes == 0) { + SHOW_DEBUG("END!!!"); + exit(0); + } +} + +static int cur_queue; // current queue +QUEUE_WRAPPER * get_next_queue(QUEUE_WRAPPER * queue_wrap); +int canSend(QUEUE_WRAPPER * queue); +void shaping_process(rtems_task_argument argument) +{ + QUEUE_WRAPPER *queue_wrap = (QUEUE_WRAPPER *) argument; + cur_queue = 0; + + while (1) { + QUEUE_WRAPPER *q_w; + q_w = get_next_queue(queue_wrap); + + if (canSend(q_w)) { + if (q_w->hasData) { + scratch_queue_shaperRW(q_w->q); + q_w->hasData--; + } + } + } + +} + +#define PULL_MODE +// queue scheduling +QUEUE_WRAPPER * get_next_queue(QUEUE_WRAPPER * queue_wrap) +{ + // round robin +#ifdef PULL_MODE + cur_queue = (cur_queue == 0) ? NUMBER_OF_QUEUES : cur_queue; + cur_queue --; +#else // PUSH MODE + cur_queue++; + cur_queue = (cur_queue == NUMBER_OF_QUEUES) ? 0 : cur_queue; +#endif + return &queue_wrap[cur_queue]; +} + + +// shaping +int canSend(QUEUE_WRAPPER * queue) +{ + return 1; +} + + + diff --git a/dol/src/dol/visitor/rtems/lib/traffic_shaping.h b/dol/src/dol/visitor/rtems/lib/traffic_shaping.h new file mode 100644 index 0000000..2ff49b8 --- /dev/null +++ b/dol/src/dol/visitor/rtems/lib/traffic_shaping.h @@ -0,0 +1,17 @@ +#ifndef TRAFFIC_SHAPING_H +#define TRAFFIC_SHAPING_H + +#include "scratch_queue.h" + +typedef struct _queue_wrapper { + SCRATCH_QUEUE_SHAPER *q; + int hasData; + QUEUE_SEMAPHORE sem_terminate; +} QUEUE_WRAPPER; + +#define @NUMBER_OF_QUEUES@ // generated +#define @NUMBER_OF_SINKS@ // generated + +rtems_task shaping_init(rtems_task_argument argument); + +#endif diff --git a/dol/src/dol/visitor/systemC/MakefileVisitor.java b/dol/src/dol/visitor/systemC/MakefileVisitor.java new file mode 100644 index 0000000..c53f585 --- /dev/null +++ b/dol/src/dol/visitor/systemC/MakefileVisitor.java @@ -0,0 +1,72 @@ +/* $Id: MakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.systemC; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a SystemC package Makefile. + */ +public class MakefileVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of the Makefile + */ + public MakefileVisitor(String dir) { + _dir = dir; + } + + /** + * Create a Makefile for the given process network. + * + * @param x process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + + ps.println("CXX = g++"); + ps.println("CC = g++"); + ps.println(); + ps.println("SYSTEMC_INC = -I" + _ui.getSystemCINC()); + ps.println("SYSTEMC_LIB = " + _ui.getSystemCLIB()); + ps.println("MY_LIB_INC = -Ilib -Isc_wrappers -Iprocesses"); + ps.println("VPATH = lib:sc_wrappers:processes"); + ps.println(); + ps.println("CXXFLAGS = -g -O0 -D__DOL_ETHZ_GEN__ $(SYSTEMC_INC) $(MY_LIB_INC)"); + ps.println("CFLAGS = $(CXXFLAGS)"); + ps.println(); + + ps.print("PROCESS_OBJS = dol.o "); + for (String basename : x.getProcessBasenames()) { + ps.print(basename + "_wrapper.o "); + } + ps.println(); + ps.println(); + ps.println("all:" + _name); + ps.println(); + ps.println(_name + ": " + _name + ".o $(PROCESS_OBJS)"); + ps.println("\t$(CXX) $(CXXFLAGS) -o $@ $^ $(SYSTEMC_LIB)"); + ps.println("clean:"); + ps.println("\t-rm -f *.o core core.* *.core " + _name); + + } catch (Exception e) { + System.out.println(" SystemC Makefile Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + + } + + protected String _dir = null; + protected String _name = "sc_application"; +} diff --git a/dol/src/dol/visitor/systemC/PNSystemCVisitor.java b/dol/src/dol/visitor/systemC/PNSystemCVisitor.java new file mode 100644 index 0000000..e043782 --- /dev/null +++ b/dol/src/dol/visitor/systemC/PNSystemCVisitor.java @@ -0,0 +1,101 @@ +/* $Id: PNSystemCVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.systemC; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a SystemC package. + */ +public class PNSystemCVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param packageName Name of the SystemC directory + */ + public PNSystemCVisitor(String packageName) { + _packageName = packageName; + } + + /** + * + * @param x process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork x) { + try { + //_packageName = x.getName() + "SystemCPackage"; + _generateDirHierarchy(); + + x.accept(new MakefileVisitor(_srcDir)); + x.accept(new SCModuleVisitor(_srcDir)); + x.accept(new ProcessVisitor(_wrapperDir)); + + } + catch (Exception e) { + System.out.println(" SystemC PN Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _generateDirHierarchy() + throws IOException, FileNotFoundException { + File dir = new File(_packageName); + dir.mkdirs(); + + _srcDir = _packageName + _delimiter + _srcDirName; + dir = new File(_srcDir); + dir.mkdirs(); + + _libDir = _srcDir + _delimiter + _libDirName; + dir = new File(_libDir); + dir.mkdirs(); + + _processDir = _srcDir + _delimiter + _processDirName; + dir = new File(_processDir); + dir.mkdirs(); + + _wrapperDir = _srcDir + _delimiter + _wrapperDirName; + dir = new File(_wrapperDir); + dir.mkdirs(); + + //copy library files + File source = new File(_ui.getMySystemCLib()); + File destination = new File(_libDir); + new Copier().copy(source, destination); + + //copy process source code + source = new File(_srcDirName); + destination = new File(_processDir); + new Copier().copy(source, destination); + } + + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; + + protected String _libDir = ""; + protected static String _libDirName = "lib"; + + protected String _processDir = ""; + protected static String _processDirName = "processes"; + + protected String _wrapperDir = ""; + protected static String _wrapperDirName = "sc_wrappers"; + + protected String _threadPostfix = "_thread"; + + protected CodePrintStream _mainPS = null; +} diff --git a/dol/src/dol/visitor/systemC/ProcessVisitor.java b/dol/src/dol/visitor/systemC/ProcessVisitor.java new file mode 100644 index 0000000..f87545b --- /dev/null +++ b/dol/src/dol/visitor/systemC/ProcessVisitor.java @@ -0,0 +1,351 @@ +/* $Id: ProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.systemC; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * a SystemC wrapper for a process: process_wrapper.[h/cpp]. + */ +public class ProcessVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir target directory + */ + public ProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param p process that needs to be rendered + */ + public void visitComponent(Process p) { + try { + _createCppFile(p); + _createHeaderFile(p); + _adaptSources(p); + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _createCppFile(Process p) throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#include \"" + p.getBasename() + + "_wrapper.h\""); + ps.printPrefixln(); + + if (p.hasOutPorts()) { + //DOL_write() + ps.printPrefixln("static inline int DOL_write(void *port, " + + "void *buf, int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("char *str = (char *)buf;"); + ps.printPrefixln("while (len-- > 0) "); + ps.printLeftBracket(); + + int j = 0; + for (Port ip : p.getPortList()) { + if (ip.isOutPort()) { + String s = (j++ == 0) ? "if " : " else if "; + ps.printPrefixln(s + "(strstr(\" OUTPORT_" + + ip.getName() + "\", (const char*)port)) "); + ps.printLeftBracket(); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))->OUTPORT_" + + ip.getName() + "->write(*(str++));"); + ps.printRightBracket(); + } + } + ps.printRightBracket(); + ps.printRightBracket(); + + //DOL_wtest() + ps.printPrefixln("static inline int DOL_wtest(void *port, " + + "int len, DOLProcess *process)"); + ps.printLeftBracket(); + j = 0; + for (Port ip : p.getPortList()) { + if (ip.isOutPort()) { + String s = (j++ == 0) ? "if " : "else if "; + ps.printPrefixln(s + "(strstr(\" OUTPORT_" + + ip.getName() + "\", (const char*)port)) "); + ps.printLeftBracket(); + ps.printPrefixln("return (static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))->OUTPORT_" + + ip.getName() + "->wtest(len);"); + ps.printRightBracket(); + } + } + ps.printRightBracket(); + } + + ps.printPrefixln(); + if (p.hasInPorts()) { + //DOL_read() + ps.printPrefixln("static inline int DOL_read(void *port, " + + "void *buf, int len, DOLProcess *process)"); + ps.printLeftBracket(); + ps.printPrefixln("char *str = (char *)buf;"); + ps.printPrefixln("while (len-- > 0)"); + ps.printLeftBracket(); + + int j = 0; + for (Port ip : p.getPortList()) { + if (ip.isInPort()) { + String s = (j++ == 0) ? "if " : "else if "; + ps.printPrefixln(s + "(strstr(\" INPORT_" + + ip.getName() + "\", (const char*)port))"); + ps.printLeftBracket(); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))->INPORT_" + + ip.getName() + "->read(*(str++));"); + ps.printRightBracket(); + } + } + ps.printRightBracket(); + ps.printRightBracket(); + + //DOL_rtest() + ps.printPrefixln("static inline int DOL_rtest(void *port, " + + "int len, DOLProcess *process)"); + ps.printLeftBracket(); + j = 0; + for (Port ip : p.getPortList()) { + if (ip.isInPort()) { + String s = (j++ == 0) ? "if " : " else if "; + ps.printPrefixln(s + "(strstr(\" INPORT_" + + ip.getName() + "\", (const char*)port))"); + ps.printLeftBracket(); + ps.printPrefixln("return (static_cast<" + p.getBasename() + + "_wrapper *>(process->wptr))->INPORT_" + + ip.getName() + "->rtest(len);"); + ps.printRightBracket(); + } + } + ps.printRightBracket(); + + } + + ps.printPrefixln(); + ps.printPrefixln("static inline int DOL_detach(DOLProcess *p)"); + ps.printLeftBracket(); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))" + "->setDetached();"); + ps.printRightBracket(); + ps.printPrefixln(); + + ps.printPrefixln("#define GETINDEX(dimension) \\"); + ps.printPrefixln("(static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->_processIndex[dimension]"); + ps.printPrefixln(); + + //include c file + for (SourceCode sr : p.getSrcList()) { + ps.printPrefixln("#include \"" + sr.getLocality() + "\""); + } + ps.printPrefixln(); + + + ps.printPrefixln("void " + p.getBasename() + + "_wrapper::setDetached() { _detached = 1; }"); + ps.printPrefixln("int " + p.getBasename() + + "_wrapper::isDetached() { return _detached; }"); + ps.printPrefixln(); + + //constructor + ps.printPrefixln(p.getBasename() + "_wrapper::" + p.getBasename() + + "_wrapper(sc_module_name name=sc_gen_unique_name(\"" + + p.getBasename() + "\" )) : sc_module(name), " + // + "_process(" + p.getBasename() + "), " + + "_detached(0)"); + ps.printLeftBracket(); + ps.printPrefixln("_state = (LocalState)new " + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State;"); + //ps.printPrefixln("struct _local_states *_state = " + // + "new struct _local_states;"); + //ps.printPrefixln("memcpy(_state, " + p.getBasename() + // + ".local, sizeof(struct _local_states));"); + ps.printPrefixln("_process.local = _state;"); + //ps.printPrefixln("sprintf(_process.local->id, name);"); + ps.printPrefixln("_process.init = " + p.getBasename() + "_init;"); + ps.printPrefixln("_process.fire = " + p.getBasename() + "_fire;"); + ps.printPrefixln("_process.wptr = this;"); + ps.printPrefixln(); + ps.printPrefixln("char buffer[255];"); + ps.printPrefixln("sprintf(buffer, name);"); + ps.printPrefixln("for (int i = 0; i < 4; i++)"); + ps.printPrefixln(" _processIndex[i] = " + + "getIndex(buffer, \"_\", i);"); + + ps.printPrefixln(); + ps.printRightBracket(); + + ps.printPrefixln(); + ps.printPrefixln("void " + p.getBasename() + + "_wrapper::initialize()"); + ps.printLeftBracket(); + ps.printPrefixln("_process.init(&_process);"); + ps.printRightBracket(); + ps.printPrefixln(); + ps.printPrefixln("int " + p.getBasename() + "_wrapper::fire()"); + ps.printLeftBracket(); + ps.printPrefixln("return _process.fire(&_process);"); + ps.printRightBracket(); + ps.printPrefixln(); + ps.printPrefixln(p.getBasename() + "_wrapper::~" + p.getBasename() + + "_wrapper()"); + ps.printLeftBracket(); + ps.printPrefixln("if (_state)"); + ps.printLeftBracket(); + ps.printPrefixln("delete (" + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State*)_state;"); + ps.printRightBracket(); + ps.printRightBracket(); + } + + protected void _createHeaderFile(Process p) + throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + ps.printPrefixln("#ifndef " + p.getBasename() + "_WRAPPER_H"); + ps.printPrefixln("#define " + p.getBasename() + "_WRAPPER_H"); + ps.printPrefixln(); + ps.printPrefixln("#include \"systemc.h\""); + ps.printPrefixln(); + ps.printPrefixln("#include \"dol_sched_if.h\""); + ps.printPrefixln("#include \"dol_fifo_if.h\""); + ps.printPrefixln("#include \"simple_fifo.h\""); + ps.printPrefixln(); + ps.printPrefixln("#include "); + ps.printPrefixln(); + ps.printPrefixln("class " + p.getBasename() + + "_wrapper : virtual public dol_sched_if, " + + "public sc_module"); + ps.printLeftBracket(); + ps.printPrefixln("public:"); + + for (Port pr : p.getPortList()) { + if (pr.isOutPort()) { + ps.printPrefixln("sc_port OUTPORT_" + + pr.getName() + ";"); + } + else if (pr.isInPort()) { + ps.printPrefixln("sc_port INPORT_" + + pr.getName() + ";"); + } + } + ps.printPrefixln("int _processIndex[4];"); + + ps.printPrefixln(); + ps.printPrefixln("" + p.getBasename() + + "_wrapper(sc_module_name name);"); + ps.printPrefixln(); + ps.printPrefixln("virtual ~" + p.getBasename() + "_wrapper();"); + ps.printPrefixln(); + ps.printPrefixln("// DOL scheduler interface"); + ps.printPrefixln("void initialize();"); + ps.printPrefixln("int fire();"); + ps.printPrefixln("void setDetached();"); + ps.printPrefixln("int isDetached();"); + ps.printPrefixln(); + ps.printPrefixln(); + ps.printPrefixln("protected:"); + ps.printPrefixln("LocalState _state;"); + + ps.printPrefixln(); + ps.printPrefixln("private:"); + ps.printPrefixln("" + p.getBasename() + "_wrapper( const " + + p.getBasename() + "_wrapper& );"); + ps.printPrefixln("" + p.getBasename() + "_wrapper& operator = " + + "( const " + p.getBasename() + "_wrapper& );"); + ps.printPrefixln("DOLProcess _process;"); + ps.printPrefixln("int _detached;"); + ps.printRightBracket(); + ps.printPrefixln(";"); + ps.printPrefixln("#endif"); + } + + /** + * Make modifications to source files of a process. + * Port names need to be strings for the SystemC code generation. + * Therefore, in the header files integer port names are put into + * quotation marks. + * + * @param p process whose sources should be adapted + * @throws IOException + */ + protected void _adaptSources(Process p) throws IOException { + Sed sed = new Sed(); + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(). + replaceAll("(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + sed.sed(processHeaderFile, + "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?[ ]*$", + "$1 " + "\"" + port.getBasename() + "\""); + } + } + } + + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/systemC/SCModuleVisitor.java b/dol/src/dol/visitor/systemC/SCModuleVisitor.java new file mode 100644 index 0000000..006a08c --- /dev/null +++ b/dol/src/dol/visitor/systemC/SCModuleVisitor.java @@ -0,0 +1,232 @@ +/* $Id: SCModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.systemC; + +import java.io.FileOutputStream; +import java.io.OutputStream; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the top sc module: sc_applicaion.cpp. + */ +public class SCModuleVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param dir path of this file + */ + public SCModuleVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + String filename = _dir + _delimiter + "sc_application.cpp"; + OutputStream file = new FileOutputStream(filename); + _mainPS = new CodePrintStream(file); + + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#include "); + _mainPS.printPrefixln("#include \"dol_fifo.h\""); + _mainPS.printPrefixln("#include \"dol_sched_if.h\""); + _mainPS.println(); + + for (String basename : x.getProcessBasenames()) { + _mainPS.printPrefixln("#include \"" + basename + + "_wrapper.h\""); + } + _mainPS.println(); + _mainPS.printPrefixln("using namespace std;"); + + _mainPS.println(); + _mainPS.printPrefixln("class sc_application : public sc_module "); + _mainPS.printLeftBracket(); + + _mainPS.printPrefixln("public:"); + _mainPS.printPrefixln("SC_HAS_PROCESS(sc_application);"); + + //declare processes + _mainPS.println(); + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln(p.getBasename() + "_wrapper " + + p.getName() + "_ins"+ ";"); + _mainPS.printPrefixln("sc_event " + p.getName() + + "_event;"); + } + _mainPS.println(); + + //define the scheduler + _mainPS.printPrefixln("sc_event sched_event;"); + _mainPS.printPrefixln("list eventList;"); + _mainPS.printPrefixln("list::iterator iter;"); + _mainPS.println(); + + //declare channels + _mainPS.println(); + for (Channel p : x.getChannelList()) { + _mainPS.printPrefixln("fifo " + p.getName() + "_ins;"); + } + _mainPS.println(); + + //model constructor + _mainPS.printPrefixln("sc_application(sc_module_name name)"); + + //parameter of constructor + _mainPS.printPrefix(": sc_module(name)"); + for (Process p : x.getProcessList()) { + _mainPS.println(","); + _mainPS.printPrefix(p.getName() + "_ins(\"" + + p.getName() +"\")"); + } + + if (x.getChannelList().size() > 0) { + for (Channel c : x.getChannelList()) { + _mainPS.println(","); + _mainPS.printPrefix(c.getName() + "_ins(" + + "\"" + c.getName() + "\", " + + c.getSize() * c.getTokenSize() + + ")"); + } + } + _mainPS.println(""); + _mainPS.printLeftBracket(); + + //construtor content + //build the network + for (Channel p : x.getChannelList()) { + p.accept(this); + } + _mainPS.println(""); + + _mainPS.println(""); + + _mainPS.printPrefixln("SC_THREAD(thread_init);"); + //init thread + _mainPS.printPrefixln("SC_THREAD(thread_sched);"); + + //declare concurrent non-terminating threads + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln("SC_THREAD(thread_" + + p.getName() + ");"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + //define scheduler thread + _mainPS.printPrefixln("void thread_init()"); + _mainPS.printLeftBracket(); + //init + for (Process p : x.getProcessList()) { + _mainPS.printPrefixln(p.getName() + "_ins.initialize();"); + } + _mainPS.printRightBracket(); + _mainPS.println(); + + + //different scheduling algorithm can be put here + _mainPS.printPrefixln("void thread_sched()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (1)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("for (iter=eventList.begin(); iter != " + + "eventList.end(); ++iter)"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("sc_event* e = (*iter);"); + _mainPS.printPrefixln("notify(*e);"); + _mainPS.printRightBracket(); + _mainPS.printPrefixln("eventList.clear();"); + _mainPS.printPrefixln("wait(sched_event);"); + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + _mainPS.println(); + + //define threads + for (Process p : x.getProcessList()) { + p.accept(this); + } + + _mainPS.printRightBracket(); // end of class + _mainPS.println(";"); + + //create and run the simulator + _mainPS.printPrefixln("int sc_main (int argc, char *argv[])"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("sc_report_handler::set_actions(\"" + + "/IEEE_Std_1666/deprecated\", SC_DO_NOTHING);"); + _mainPS.printPrefixln("sc_report::register_id(" + + "RP_ID_PARAMETER_PROBLEM, " + + "\"parameter problem\" );"); + + //create an instance of the application model + //remove potential whitespaces before using the process + //network name as a systemc identifier + _mainPS.printPrefixln("sc_application my_app_mdl(\"" + + x.getName().replaceAll(" ", "") + "\");"); + _mainPS.printPrefixln("sc_start(-1,SC_NS);"); + _mainPS.printPrefixln("return 0;"); + + _mainPS.printRightBracket(); + + } + catch (Exception e) { + System.out.println(" SystemC module visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Print a line for the process in the correct format for DOTTY. + * + * @param x process that needs to be rendered + */ + public void visitComponent(Process x) { + _mainPS.printPrefixln("void thread_" + x.getName() + "()"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln("while (!" + x.getName() + + "_ins.isDetached())"); + _mainPS.printLeftBracket(); + _mainPS.printPrefixln(x.getName() + "_ins.fire();"); + _mainPS.printPrefixln("eventList.push_back(&" + x.getName() + + "_event);"); + _mainPS.printPrefixln("sched_event.notify();"); + _mainPS.printPrefixln("wait(" + x.getName() + "_event);"); + + _mainPS.printRightBracket(); + _mainPS.printRightBracket(); + } + + /** + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + for (Port p : x.getPortList()) { + if (p.isOutPort()) { + //we get port name from channel + //channel.out == process.in + _mainPS.printPrefixln(p.getPeerResource().getName() + + "_ins.INPORT_" + p.getPeerPort().getName() + + "(" + x.getName() + "_ins);"); + } else if (p.isInPort()) { + _mainPS.printPrefixln(p.getPeerResource().getName() + + "_ins.OUTPORT_" + p.getPeerPort().getName() + + "(" + x.getName() + "_ins);"); + } + } + } + + protected CodePrintStream _mainPS = null; + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/systemC/lib/dol.c b/dol/src/dol/visitor/systemC/lib/dol.c new file mode 100644 index 0000000..d852cf5 --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/dol.c @@ -0,0 +1,81 @@ +#include "dol.h" + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * Example: createPort(xxx, "port", 2, 1, N1, 3, N2) will result in xxx + * having the value "port_1_3". The range definitions N1, N2 will be ignored + * in this implementation + * + * @param port buffer where the result is stored + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +char* createPort(char* port, char* base, + int number_of_indices, int indices, ...) { + char next_index_string[10]; + strcpy(port, base); + + if (number_of_indices > 4) { + printf("Error: iterated ports must not have more than "); + printf("4 dimensions.\n"); + exit(-1); + } + + va_list argumentlist; + va_start(argumentlist, indices); + + if (number_of_indices > 0) { + sprintf(next_index_string, "_%d", indices); + strcat(port, next_index_string); + + for (int k = 1; k < number_of_indices; k++) { + va_arg(argumentlist, int); //skip the range value + sprintf(next_index_string, "_%d", va_arg(argumentlist, int)); + strcat(port, next_index_string); + } + } + return port; +} + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int getIndex(const char* string, char* tokens, int indexNumber) { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + return -1; +} diff --git a/dol/src/dol/visitor/systemC/lib/dol.h b/dol/src/dol/visitor/systemC/lib/dol.h new file mode 100644 index 0000000..282bc4d --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/dol.h @@ -0,0 +1,77 @@ +#ifndef DOL_H +#define DOL_H + +#include +#include +#include +#include + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * - Local variables for each process can be defined in the structure + * LocalState. For each process, a new instance of LocalState is + * instantiated. + * - The ProcessInit function pointer points to the function which is + * called to initialize a process. + * - The ProcessFire function pointer points to the function which is + * called repeatedly by the scheduler. + * - The wptr is a placeholder for callback. A pointer to the wrapper + * class instance of the process can be assigned. This is done in the + * code generated by the code generation, so one can just leave it + * blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + + +//macros to deal with iterated ports + +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define MAX_PORT_NAME_LENGTH 255 +#define CREATEPORTVAR(name) char name[MAX_PORT_NAME_LENGTH] + + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +#define CREATEPORT(port, base, number_of_indices, indices...) \ + createPort(port, base, number_of_indices, indices) + +char* createPort(char* port, + char* base, + int number_of_indices, + int indices, ...); + +int getIndex(const char* string, char* tokens, int indexNumber); + +#endif diff --git a/dol/src/dol/visitor/systemC/lib/dol_fifo.h b/dol/src/dol/visitor/systemC/lib/dol_fifo.h new file mode 100644 index 0000000..5646682 --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/dol_fifo.h @@ -0,0 +1,432 @@ +/************************************************************************** + dol_fifo.h + + DOL FIFO channel: + relative bloking (RB) and relative non-bloking (RN) + task transaction level (TTL) fifo channel + + (c) 2006 by Alexander Maxiaguine + + Computer Engineering and Networks Laboratory, TIK + Swiss Federal Institute of Technology, ETHZ Zurich + Switzerland + +**************************************************************************/ + +/************************************************************************** + Change Log: + + 14.03.06 -- creation + +**************************************************************************/ + +#ifndef DOL_FIFO_H +#define DOL_FIFO_H + +#include +#include "systemc.h" + +#include "dol_rp_ids.h" +#include "dol_fifo_if.h" + + +template +class dol_fifo +: public dol_fifo_write_if, + public dol_fifo_read_if, + public sc_prim_channel +{ +public: + + // constructors + explicit dol_fifo( int size ) : sc_prim_channel( sc_gen_unique_name( "dol_fifo" ) ) + { init( size ); } + + explicit dol_fifo( const char* name, int size) : sc_prim_channel( name) + { init( size ); } + + // destructor + virtual ~dol_fifo() + { delete [] m_buf; } + + virtual void register_port( sc_port_base&, const char* ); + + // ********* WRITE interface methods ************** + // bloking (re)acquire free room in the channel + virtual void reAcquireRoom(int count); + + // non-bloking (re)acquire free room in the channel + virtual bool tryReAcquireRoom(int count); + + // store data into the acquired free room + virtual void store(int offset, T* data, int count); + + // release the stored data to the channel (commit write) + virtual void releaseData(int count); + + // ********* READ interface methods ************** + // bloking (re)acquire data in the channel + virtual void reAcquireData(int count); + + // non-bloking (re)acquire data in the channel + virtual bool tryReAcquireData(int count); + + // load the acquired data into a vector + virtual void load(int offset, T* vector, int count); + + // free up the room in the channel (commit read) + virtual void releaseRoom(int count); + + + // *************************************** + // helper methods (for debug, etc.) + + bool in_deadlock() { + return reader_is_blocked && writer_is_blocked; + } + + void trace( sc_trace_file* tf ) const; + virtual void print( ::std::ostream& = ::std::cout ) const; + virtual void dump( ::std::ostream& = ::std::cout ) const; + + + virtual const char* kind() const + { return "dol_fifo"; } + + +protected: + + virtual void update(); + void init( int ); + + +protected: + + int m_size; // buffer size + T* m_buf; // the buffer + + // channel state variables + int m_rwin_head; // index of read window head (oldest full token) + int m_wwin_head; // index of write window head (oldest empty token) + + int m_readable; // number of readable tokens (number of full tokens) + int m_acquired_room; // size of acquired room (write window) + int m_acquired_data; // size of acquired data (read window) + + int m_released_data; // size of data (filled tokens) released during this delta cycle + int m_released_room; // size of room (emptied tokens) released during this delta cycle + + + // SC specific properties + sc_event m_data_released_event; + sc_event m_room_released_event; + + sc_port_base* m_reader; // used for static design rule checking + sc_port_base* m_writer; // used for static design rule checking + + // used to detect deadlocks + bool reader_is_blocked; + bool writer_is_blocked; + +private: + + // disabled + dol_fifo( const dol_fifo& ); + dol_fifo& operator = ( const dol_fifo& ); +}; + + + + +// ************ Implementation ************************************ + +// *** WRITE interface methods **** + +// blocking (re)acquire free room of size COUNT in the channel +template +inline void dol_fifo::reAcquireRoom( int count ) +{ + while( m_size - m_readable < count ) { + if( m_size < count) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Requested room is larger than total available buffer space \n --> This is a deadlock\n\n"); + } + + writer_is_blocked = true; + sc_core::wait( m_room_released_event ); + writer_is_blocked = false; + } + m_acquired_room = count; +} + +// non-bloking (re)acquire free room in the channel +template +inline bool dol_fifo::tryReAcquireRoom( int count ) +{ + if( m_size - m_readable < count ) { + if( m_size < count) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Requested room is larger than the total available buffer space \n\n"); + } + return false; + } + m_acquired_room = count; + return true; +} + +// write COUNT data into the acquired free room starting from the position indicated by OFFSET +template +inline void dol_fifo::store( int offset, T* data, int count ) +{ + // sanity checks + if( m_acquired_room <= 0) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to write without acquiring the room\n --> I ignore this action\n\n"); + return; + } + if( offset > m_acquired_room - 1 ) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to write beyond the acquired room: offset value may be too large\n --> I ignore this action\n\n"); + return; + } + if( count > m_acquired_room - offset ) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to write beyound the acquired room: data size may be too large\n --> I'm decreasing the data size\n\n"); + count = m_acquired_room - offset; + } + + int wi = ( m_wwin_head + offset ) % m_size; + for(int i=0; i +inline void dol_fifo::releaseData( int count ) +{ + // sanity check + if( count > m_acquired_room ) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to release more room than was acquired\n --> I'm decreasing the size of released room\n\n"); + count = m_acquired_room; + } + + m_readable += count; + m_released_data += count; + + m_acquired_room -= count; + m_wwin_head = ( m_wwin_head + count ) % m_size; + + request_update(); +} + + +// *** READ interface methods **** + +// bloking (re)acquire COUNT data in the channel +template +inline void dol_fifo::reAcquireData( int count ) +{ + while( m_readable < count ) { + if( m_size < count) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Acquired data size is larger than total available buffer space\n --> This is a deadlock\n\n"); + } + + reader_is_blocked = true; + sc_core::wait( m_data_released_event ); + reader_is_blocked = false; + } + + m_acquired_data = count; +} + +// non-bloking (re)acquire COUNT data in the channel +template +inline +bool +dol_fifo::tryReAcquireData( int count ) { + if( m_readable < count ) { + + if( m_size < count) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Acquired data size is larger than total available buffer space \n\n"); + } + return false; + } + + m_acquired_data = count; + return true; +} + +// load COUNT acquired data into a vector +// from the read window starting at the position indicated by OFFSET +template +inline void dol_fifo::load( int offset, T* vector, int count ) +{ + // sanity checks + if( m_acquired_data <= 0) { + + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to load without acquiring the data\n --> I ignore this action\n\n"); + return; + } + + if( offset > m_acquired_data - 1 ) { + + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to read beyond the acquired data: offset value may be too large\n --> I ignore this action\n\n"); + return; + } + + if( count > m_acquired_data - offset ) { + + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to read beyound the acquired data: data size may be too large\n --> I'm decreasing the data size\n\n"); + count = m_acquired_data - offset; + } + + + int ri = ( m_rwin_head + offset ) % m_size; + + for(int i=0; i +inline void dol_fifo::releaseRoom( int count ) +{ + // sanity check + if( count > m_acquired_data ) { + SC_REPORT_WARNING(RP_ID_PARAMETER_PROBLEM, "Attempt to free up more room than was acquired\n --> I'm decreasing the size of released room\n\n"); + count = m_acquired_data; + } + + m_readable -= count; + m_released_room += count; + + m_acquired_data -= count; + m_rwin_head = ( m_rwin_head + count ) % m_size; + + request_update(); +} + + + + +// *** Other methods *** + + +template +inline void dol_fifo::update() +{ + if( m_released_room > 0 ) { + m_room_released_event.notify_delayed(); + } + + if( m_released_data > 0 ) { + m_data_released_event.notify_delayed(); + } + + m_released_room = 0; + m_released_data = 0; +} + + +template +inline void dol_fifo::init( int size ) +{ + if( size <= 0 ) { + SC_REPORT_ERROR( SC_ID_INVALID_FIFO_SIZE_, 0 ); + } + + m_size = size; + m_buf = new T[m_size]; + + m_readable = 0; + + m_acquired_room = 0; + m_acquired_data = 0; + + m_rwin_head = 0; + m_wwin_head = 0; + + m_released_data = 0; + m_released_room = 0; + + m_reader = 0; + m_writer = 0; + + reader_is_blocked = false; + writer_is_blocked = false; +} + + +template +inline void dol_fifo::register_port( sc_port_base& port_, + const char* if_typename_ ) +{ + std::string nm( if_typename_ ); + if( nm == typeid( dol_fifo_read_if ).name() ) { + // only one reader can be connected + if( m_reader != 0 ) { + SC_REPORT_ERROR( SC_ID_MORE_THAN_ONE_FIFO_READER_, 0 ); + } + m_reader = &port_; + } else { // nm == typeid( dol_fifo_write_if ).name() + // only one writer can be connected + if( m_writer != 0 ) { + SC_REPORT_ERROR( SC_ID_MORE_THAN_ONE_FIFO_WRITER_, 0 ); + } + m_writer = &port_; + } +} + + + +template +inline void dol_fifo::trace( sc_trace_file* tf ) const +{ +#ifdef DEBUG_SYSTEMC + char buf[32]; + std::string nm = name(); + for( int i = 0; i < m_size; ++ i ) { + sprintf( buf, "_%d", i ); + sc_trace( tf, m_buf[i], nm + buf ); + } +#endif +} + + +template +inline void dol_fifo::print( ::std::ostream& os ) const +{ + if( m_readable ) { + for(int j=0, i = m_rwin_head; j +inline void dol_fifo::dump( ::std::ostream& os ) const +{ + os << "name = " << name() << ::std::endl; + if( m_readable ) { + for(int j=0, i = m_rwin_head; j +class dol_fifo_write_if : virtual public sc_interface +{ +public: + + // bloking (re)acquire free room in the channel + virtual void reAcquireRoom(int count) = 0; + + // non-bloking (re)acquire free room in the channel + virtual bool tryReAcquireRoom(int count) = 0; + + // store data into the acquired free room + virtual void store(int offset, T* data, int count) = 0; + + // release the stored data to the channel (commit write) + virtual void releaseData(int count) = 0; + + +protected: + + // constructor + dol_fifo_write_if() {} + + +private: + + // disabled + dol_fifo_write_if( const dol_fifo_write_if& ); + dol_fifo_write_if& operator = ( const dol_fifo_write_if& ); +}; + +template +class dol_fifo_read_if : virtual public sc_interface +{ +public: + + // bloking (re)acquire data in the channel + virtual void reAcquireData(int count) = 0; + + // non-bloking (re)acquire data in the channel + virtual bool tryReAcquireData(int count) = 0; + + // load the acquired data into a vector + virtual void load(int offset, T* vector, int count) = 0; + + // free up the room in the channel (commit read) + virtual void releaseRoom(int count) = 0; + + +protected: + + // constructor + dol_fifo_read_if() {} + + +private: + + // disabled + dol_fifo_read_if( const dol_fifo_read_if& ); + dol_fifo_read_if& operator = ( const dol_fifo_read_if& ); +}; + + +#endif diff --git a/dol/src/dol/visitor/systemC/lib/dol_rp_ids.h b/dol/src/dol/visitor/systemC/lib/dol_rp_ids.h new file mode 100644 index 0000000..1bd44c5 --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/dol_rp_ids.h @@ -0,0 +1,28 @@ +/************************************************************************** + dol_rp_ids.h + + Report IDs + + (c) 2006 by Alexander Maxiaguine + + Computer Engineering and Networks Laboratory, TIK + Swiss Federal Institute of Technology, ETHZ Zurich + Switzerland + +**************************************************************************/ + +/************************************************************************** + Change Log: + + 14.03.06 -- creation + +**************************************************************************/ + +#ifndef DOL_RP_IDS_H +#define DOL_RP_IDS_H + +//----------------- REPORT IDs -------------------------- +const int RP_ID_PARAMETER_PROBLEM = 5000; + + +#endif diff --git a/dol/src/dol/visitor/systemC/lib/dol_sched_if.h b/dol/src/dol/visitor/systemC/lib/dol_sched_if.h new file mode 100644 index 0000000..1856731 --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/dol_sched_if.h @@ -0,0 +1,46 @@ +/************************************************************************** + dol_sched_if.h + + Scheduler interface for a DOL process + + (c) 2006 by Alexander Maxiaguine + + Computer Engineering and Networks Laboratory, TIK + Swiss Federal Institute of Technology, ETHZ Zurich + Switzerland + +**************************************************************************/ + +/************************************************************************** + Change Log: + + 14.03.06 -- creation + +**************************************************************************/ + +#ifndef DOL_SCHED_IF_H +#define DOL_SCHED_IF_H + +#include "systemc.h" + + +class dol_sched_if +{ + +public: + virtual void initialize() = 0; + virtual int fire() = 0; + + +protected: + dol_sched_if() {} + + +private: + + // disabled + dol_sched_if( const dol_sched_if& ); + dol_sched_if& operator = ( const dol_sched_if& ); +}; + +#endif diff --git a/dol/src/dol/visitor/systemC/lib/simple_fifo.h b/dol/src/dol/visitor/systemC/lib/simple_fifo.h new file mode 100644 index 0000000..d11aecf --- /dev/null +++ b/dol/src/dol/visitor/systemC/lib/simple_fifo.h @@ -0,0 +1,182 @@ +/***************************************************************************** + + The following code is derived, directly or indirectly, from the SystemC + source code Copyright (c) 1996-2004 by all Contributors. + All Rights reserved. + + The contents of this file are subject to the restrictions and limitations + set forth in the SystemC Open Source License Version 2.4 (the "License"); + You may not use this file except in compliance with such restrictions and + limitations. You may obtain instructions on how to receive a copy of the + License at http://www.systemc.org/. Software distributed by Contributors + under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF + ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + *****************************************************************************/ + +/***************************************************************************** + + simple_fifo.cpp -- Simple SystemC 2.0 producer/consumer example. + + From "An Introduction to System Level Modeling in + SystemC 2.0". By Stuart Swan, Cadence Design Systems. + Available at www.systemc.org + + Original Author: Stuart Swan, Cadence Design Systems, 2001-06-18 + + *****************************************************************************/ + +/***************************************************************************** + + MODIFICATION LOG - modifiers, enter your name, affiliation, date and + changes you are making here. + + Name, Affiliation, Date: + Description of Modification: + + *****************************************************************************/ + +#ifndef _SIMPLE_FIFO_H_ +#define _SIMPLE_FIFO_H_ + +#include + +class write_if : virtual public sc_interface +{ + public: + virtual void write(char) = 0; + virtual void reset() = 0; + virtual int wtest(int) = 0; +}; + +class read_if : virtual public sc_interface +{ + public: + virtual void read(char &) = 0; + virtual int num_available() = 0; + virtual int rtest(int) = 0; +}; + +class fifo : public sc_channel, public write_if, public read_if +{ + public: + fifo(sc_module_name name, int size) + : sc_channel(name), num_elements(0), first(0), max(size) { + data = new char[size]; + } + + void write(char c) { + if (num_elements == max) + wait(read_event); + + data[(first + num_elements) % max] = c; + ++ num_elements; + write_event.notify(); + } + + void read(char &c){ + if (num_elements == 0) + wait(write_event); + + c = data[first]; + -- num_elements; + first = (first + 1) % max; + read_event.notify(); + } + + int rtest(int size) { return (size <= num_elements) ? 1 : 0; } + int wtest(int size) { return (size <= max - num_elements) ? 1 : 0; } + + + void reset() { num_elements = first = 0; } + + int num_available() { return num_elements;} + + private: + int max; + char *data; + int num_elements, first; + sc_event write_event, read_event; +}; + + +#endif + +/* +class producer : public sc_module +{ + public: + sc_port out; + + SC_HAS_PROCESS(producer); + + producer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + const char *str = + "Visit www.systemc.org and see what SystemC can do for you today!\n"; + + while (*str) + out->write(*str++); + } +}; + +class consumer : public sc_module +{ + public: + sc_port in; + + SC_HAS_PROCESS(consumer); + + consumer(sc_module_name name) : sc_module(name) + { + SC_THREAD(main); + } + + void main() + { + char c; + cout << endl << endl; + + while (true) { + in->read(c); + cout << c << flush; + + if (in->num_available() == 1) + cout << "<1>" << flush; + if (in->num_available() == 9) + cout << "<9>" << flush; + } + } +}; + +class top : public sc_module +{ + public: + fifo *fifo_inst; + producer *prod_inst; + consumer *cons_inst; + + top(sc_module_name name) : sc_module(name) + { + fifo_inst = new fifo("Fifo1"); + + prod_inst = new producer("Producer1"); + prod_inst->out(*fifo_inst); + + cons_inst = new consumer("Consumer1"); + cons_inst->in(*fifo_inst); + } +}; + +int sc_main (int argc , char *argv[]) { + top top1("Top1"); + sc_start(-1); + return 0; +} +*/ diff --git a/dol/src/dol/visitor/systemC/package.html b/dol/src/dol/visitor/systemC/package.html new file mode 100644 index 0000000..10fe852 --- /dev/null +++ b/dol/src/dol/visitor/systemC/package.html @@ -0,0 +1,20 @@ + + + + + + +Code generator for SystemC functional simulation. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/xml/MapXmlVisitor.java b/dol/src/dol/visitor/xml/MapXmlVisitor.java new file mode 100644 index 0000000..564715a --- /dev/null +++ b/dol/src/dol/visitor/xml/MapXmlVisitor.java @@ -0,0 +1,193 @@ +/* $Id: MapXmlVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.xml; + +import dol.datamodel.XmlTag; +import dol.datamodel.mapping.CommunicationBinding; +import dol.datamodel.mapping.ComputationBinding; +import dol.datamodel.mapping.Configuration; +import dol.datamodel.mapping.Mapping; +import dol.datamodel.mapping.Schedule; +import dol.datamodel.mapping.ScheduleEntry; +import dol.datamodel.mapping.SchedulingPolicy; +import dol.datamodel.mapping.Variable; +import dol.util.CodePrintString; +import dol.visitor.MapVisitor; + +public class MapXmlVisitor extends MapVisitor { + + /** + * Constructor. + * + * @param stringBuffer buffer where the result is stored + */ + public MapXmlVisitor(StringBuffer stringBuffer) { + _ps = new CodePrintString(stringBuffer); + } + + /** + * + * @param x mapping that needs to be rendered. + */ + public void visitComponent(Mapping x) { + String xmlns = dol.util.SchemaLocation. + getMappingNamespace(); + String xsiLocation = dol.util.SchemaLocation. + getMappingSchemaLocation(); + + _ps.println(""); + _ps.printOpeningTag(_xt.getMappingTag()); + _ps.println(" xmlns=\"" + xmlns + + "\" xmlns:xsi=\"http://www.w3.org/2001/" + + "XMLSchema-instance\"" + + System.getProperty("line.separator") + + " xsi:schemaLocation=\"" + + xmlns + " " + xsiLocation + "\" name=\"" + + x.getName() + "\">"); + + // Visit variables + for(Variable v : x.getVarList()) { + v.accept(this); + } + + // Visit computation bindings + for(ComputationBinding b : x.getCompBindList()) { + b.accept(this); + } + + // Visit communication bindings + for(CommunicationBinding b : x.getCommBindList()) { + b.accept(this); + } + + // Visit schedules + for(Schedule s : x.getScheduleList()) { + s.accept(this); + } + + _ps.printClosingTag(_xt.getMappingTag()); + } + + /** + * + */ + public void visitComponent(ComputationBinding x) { + _ps.printOpeningTag(_xt.getBindingTag()); + _ps.println(" name=\"" + x.getName() + + "\" xsi:type=\"computation\">"); + + // Process + _ps.printPrefix(); + String s = "<" + _xt.getProcessTag() + + " name=\"" + x.getProcess().getName() + "\"/>"; + _ps.println(s); + + // Processor + _ps.printPrefix(); + s = "<" + _xt.getProcessorTag() + + " name=\"" + x.getProcessor().getName() + "\"/>"; + _ps.println(s); + _ps.printClosingTag(_xt.getBindingTag()); + } + + /** + * + */ + public void visitComponent(CommunicationBinding x) { + _ps.printOpeningTag(_xt.getBindingTag()); + _ps.println(" name=\"" + x.getName() + + "\" xsi:type=\"communication\">"); + + // SW channel + _ps.printPrefix(); + String s = "<" + _xt.getSWChannelTag() + + " name=\"" + x.getChannel().getName() + "\"/>"; + _ps.println(s); + + // Write path + _ps.printPrefix(); + s = "<" + _xt.getWritePathTag() + + " name=\"" + x.getWritePath().getName() + "\"/>"; + _ps.println(s); + + // Read path + _ps.printPrefix(); + s = "<" + _xt.getReadPathTag() + + " name=\"" + x.getReadPath().getName() + "\"/>"; + _ps.println(s); + + _ps.printClosingTag(_xt.getBindingTag()); + } + + /** + * + */ + public void visitComponent(Schedule x) { + _ps.printOpeningTag(_xt.getScheduleTag()); + _ps.println(" name=\"" + x.getName() + + "\" type=\"" + + SchedulingPolicy.toString(x.getSchedPolicy()) + "\">"); + + // Resource + _ps.printPrefix(); + String s = "<" + _xt.getResourceTag() + + " name=\"" + x.getResource().getName() + "\"/>"; + _ps.println(s); + + // Scheduler table entries + for (ScheduleEntry p : x.getEntryList()) { + p.accept(this); + } + + // Configuration of schedule + for (Configuration c : x.getCfgList()) { + c.accept(this); + } + + _ps.printClosingTag(_xt.getScheduleTag()); + } + + /** + * + */ + public void visitComponent(ScheduleEntry x) { + if (x.getCfgList().size() == 0) { + _ps.printPrefixln("<" + _xt.getOriginTag() + " name=\"" + + x.getName() + "\"/>"); + } else { + _ps.printOpeningTag(_xt.getOriginTag()); + _ps.println(" name=\"" + x.getName() + "\">"); + + // Configuration of the scheduler entry + for (Configuration c : x.getCfgList()) { + c.accept(this); + } + + _ps.printClosingTag(_xt.getOriginTag()); + } + } + + /** + * + */ + public void visitComponent(Variable x) { + _ps.printPrefix(); + String s = "<" + _xt.getVariableTag() + + " name=\"" + x.getName() + "\"" + + " value=\"" + Integer.toString(x.getValue()) + "\"/>"; + _ps.println(s); + } + + /** + * + */ + public void visitComponent(Configuration x) { + _ps.printPrefix(); + String s = "<" + _xt.getConfigurationTag() + + " name=\"" + x.getName() + "\"" + + " value=\"" + x.getValue() + "\"/>"; + _ps.println(s); + } + + protected CodePrintString _ps = null; + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/visitor/xml/PNXmlVisitor.java b/dol/src/dol/visitor/xml/PNXmlVisitor.java new file mode 100644 index 0000000..19666e0 --- /dev/null +++ b/dol/src/dol/visitor/xml/PNXmlVisitor.java @@ -0,0 +1,217 @@ +/* $Id: PNXmlVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.xml; + +import dol.datamodel.XmlTag; +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Configuration; +import dol.datamodel.pn.Connection; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.ProfilingConfiguration; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintString; +import dol.visitor.PNVisitor; + +/** + * This is a class for a visitor that is used to generate + * a schema compatible XML for process network. + */ +public class PNXmlVisitor extends PNVisitor { + + /** + * Constructor. + * + * @param stringBuffer buffer where the result is stored + */ + public PNXmlVisitor(StringBuffer stringBuffer) { + _ps = new CodePrintString(stringBuffer); + } + + /** + * + * @param x process network that needs to be rendered. + */ + public void visitComponent(ProcessNetwork x) { + String xmlns = dol.util.SchemaLocation. + getProcessNetworkNamespace(); + String xsiLocation = dol.util.SchemaLocation. + getProcessNetworkSchemaLocation(); + + + _ps.println(""); + _ps.printOpeningTag(_xt.getPNTag()); + _ps.println(" xmlns=\"" + xmlns + + "\" xmlns:xsi=\"http://www.w3.org/2001/" + + "XMLSchema-instance\"" + + System.getProperty("line.separator") + + " xsi:schemaLocation=\"" + + xmlns + " " + xsiLocation + "\" name=\"" + + x.getName() + "_annotated" + "\">"); + + //visit all processes + for (Process p : x.getProcessList()) { + p.accept(this); + } + _ps.println(); + + //visit all channels + for (Channel c : x.getChannelList()) { + c.accept(this); + } + _ps.println(); + + //visit all connections + for (Connection n : x.getConnectionList()) { + n.accept(this); + } + + _ps.printClosingTag(_xt.getPNTag()); + } + + /** + * Print a line for the process in the correct format for XML. + * + * @param x process that needs to be rendered + */ + public void visitComponent(Process x) { + _ps.printOpeningTag(_xt.getProcessTag()); + _ps.println(" name=\"" + x.getName() + + "\" basename=\"" + x.getBasename() + + "\" range=\"" + x.getRange() + + "\">"); + + for (Port p : x.getPortList()) { + p.accept(this); + } + + for (SourceCode s : x.getSrcList()) { + s.accept(this); + } + + for (Configuration c : x.getCfgList()) { + c.accept(this); + } + + for (ProfilingConfiguration c : x.getProfilingList()) { + c.accept(this); + } + + _ps.printClosingTag(_xt.getProcessTag()); + } + + /** + * Print a line for the channel in the correct format for XML. + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Channel x) { + _ps.printOpeningTag(_xt.getSWChannelTag()); + _ps.println(" name=\"" + x.getName() + + "\" basename=\"" + x.getBasename() + + "\" type=\"" + x.getType() + + "\" size=\"" + x.getSize() + + "\">"); + + for (Port p : x.getPortList()) { + p.accept(this); + } + + for (Configuration c : x.getCfgList()) { + c.accept(this); + } + + for (ProfilingConfiguration c : x.getProfilingList()) { + c.accept(this); + } + + _ps.printClosingTag(_xt.getSWChannelTag()); + } + + /** + * Print a line for the connection in the correct format for XML. + * + * @param x channel that needs to be rendered + */ + public void visitComponent(Connection x) { + _ps.printOpeningTag(_xt.getConnectionTag()); + _ps.println(" name=\"" + x.getName() + "\">"); + + //origin + _ps.printOpeningTag("origin"); + _ps.println(" name=\"" + x.getOrigin().getName() + "\">"); + _ps.println(" "); + _ps.printClosingTag("origin"); + + //target + _ps.printOpeningTag("target"); + _ps.println(" name=\"" + x.getTarget().getName() + "\">"); + _ps.println(" "); + _ps.printClosingTag("target"); + _ps.printClosingTag("connection"); + } + + /** + * Print a line for the port in the correct format for XML. + * + * @param x port that needs to be rendered + */ + public void visitComponent(Port x) { + _ps.printPrefix(); + String s = "<" + _xt.getPortTag() + + " name=\"" + x.getName() + + "\" basename=\"" + x.getBasename() + + "\" range=\"" + x.getRange() + + "\""; + if (!x.getType().equals("")) + s += " type=\"" + x.getType() + "\""; + s += " />"; + + _ps.println(s); + } + + /** + * Print a line for the source code in the correct format for XML. + * + * @param x source that needs to be rendered + */ + public void visitComponent(SourceCode x) { + _ps.printPrefix(); + _ps.println("<" + _xt.getSourceTag() + + " type=\"" + x.getType() + + "\" location=\"" + x.getLocality() + + "\" />"); + } + + /** + * Print a line for the configuration in the correct format for XML. + * + * @param x configuration that needs to be rendered + */ + public void visitComponent(Configuration x) { + _ps.printPrefix(); + _ps.println("<" + _xt.getConfigurationTag() + + " name=\"" + x.getName() + + "\" value=\"" + x.getValue() + + "\" />"); + } + + /** + * Print a line for the profiling configuration in the correct format for XML. + * + * @param x configuration that needs to be rendered + */ + public void visitComponent(ProfilingConfiguration x) { + _ps.printPrefix(); + _ps.println("<" + _xt.getProfilingTag() + + " name=\"" + x.getName() + + "\" value=\"" + x.getValue() + + "\" />"); + } + + protected CodePrintString _ps = null; + + protected XmlTag _xt = XmlTag.getInstance(); +} diff --git a/dol/src/dol/visitor/xml/package.html b/dol/src/dol/visitor/xml/package.html new file mode 100644 index 0000000..a476417 --- /dev/null +++ b/dol/src/dol/visitor/xml/package.html @@ -0,0 +1,22 @@ + + + + + + +Schema compatible XML generator for process network. + +Maybe for architecture and mapping in the future. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol/visitor/yapi/YapiMakefileVisitor.java b/dol/src/dol/visitor/yapi/YapiMakefileVisitor.java new file mode 100644 index 0000000..5103ec9 --- /dev/null +++ b/dol/src/dol/visitor/yapi/YapiMakefileVisitor.java @@ -0,0 +1,76 @@ +/* $Id: YapiMakefileVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.yapi; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Vector; + +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.visitor.PNVisitor; + +/** + * + */ +public class YapiMakefileVisitor extends PNVisitor { + + /** + * + */ + public YapiMakefileVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork pn) { + try { + String filename = _dir + _delimiter + "Makefile"; + OutputStream file = new FileOutputStream(filename); + PrintStream ps = new PrintStream(file); + ps.println("YAPI = /lib/yapi"); + ps.println("INCLUDES = -I$(YAPI)/include -Iwrappers -Ilib -Iprocesses -I."); + ps.println("LIBS = $(YAPI)/lib/libyapi.a"); + ps.println("CXX = g++"); + ps.println("CXXFLAGS = -g -Wall -O $(INCLUDES)"); + ps.println("OBJS = application.o \\"); + Vector processList = new Vector(); + for (Process p : pn.getProcessList()) { + String basename = p.getBasename(); + if (!processList.contains(basename)) { + processList.add(basename); + ps.println(" wrappers/" + p.getBasename() + + "_wrapper.o \\"); + } + } + ps.println(" lib/dolSupport.o \\"); + ps.println(" lib/ProcessWrapper.o \\"); + ps.println(" lib/main.o"); + ps.println(); + ps.println("EXE = ./sc_application"); + ps.println(); + ps.println("all : $(EXE)"); + ps.println(); + ps.println("test : $(EXE)"); + ps.println("\t@$(EXE)"); + ps.println(); + ps.println("$(EXE) : $(OBJS)"); + ps.println("\t$(CXX) $(LDFLAGS) $(OBJS) $(LIBS) -o $(EXE)"); + ps.println(); + ps.println("clean : ;"); + ps.println("\trm -rf $(OBJS) $(EXE)"); + } + catch (IOException e) { + System.out.println(" Yapi Makefile Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected String _dir = null; + protected String _name = "sc_application"; +} + diff --git a/dol/src/dol/visitor/yapi/YapiModuleVisitor.java b/dol/src/dol/visitor/yapi/YapiModuleVisitor.java new file mode 100644 index 0000000..bf2f699 --- /dev/null +++ b/dol/src/dol/visitor/yapi/YapiModuleVisitor.java @@ -0,0 +1,126 @@ +/* $Id: YapiModuleVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.yapi; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Channel; +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.util.CodePrintStream; +import dol.visitor.PNVisitor; + +/** + * This class is a class for a visitor that is used to generate + * the main program. + */ +public class YapiModuleVisitor extends PNVisitor { + + /** + * Constructor. + */ + public YapiModuleVisitor(String dir) { + _dir = dir; + } + + /** + * + */ + public void visitComponent(ProcessNetwork pn) { + try { + //create header file + String filename = _dir + _delimiter + "application.h"; + OutputStream file = new FileOutputStream(filename); + _code = new CodePrintStream(file); + + _code.println("#include \"yapi.h\""); + _code.println(); + + Vector processList = new Vector(); + for (Process p : pn.getProcessList()) { + String basename = p.getBasename(); + if (!processList.contains(basename)) { + processList.add(basename); + _code.println("#include \"" + + p.getBasename() + "_wrapper.h\""); + } + } + _code.println(); + _code.println("class Application : public ProcessNetwork"); + _code.println("{"); + _code.println("public:"); + _code.println(" Application(const Id& n);"); + _code.println(" const char* type() const;"); + _code.println(); + _code.println("private:"); + + for (Channel c : pn.getChannelList()) { + if (c.getType().equals("fifo")) { + _code.println(" Fifo " + c.getName() + + ";"); + } else if (c.getType().equals("wfifo")) { + System.out.println("Warning: YAPI visitor does not " + + "support windowed FIFOs."); + } + } + _code.println(); + + for (Process p : pn.getProcessList()) { + _code.println(" " + p.getBasename() + "_wrapper " + + p.getName() + ";"); + } + _code.println("};"); + file.close(); + + + //create cpp file + filename = _dir + _delimiter + "application.cpp"; + file = new FileOutputStream(filename); + _code = new CodePrintStream(file); + + _code.println("#include \"application.h\""); + _code.println(); + _code.println("Application::Application(const Id& n) :"); + _code.print(" ProcessNetwork(n)"); + for (Channel c : pn.getChannelList()) { + if (c.getType().equals("fifo")) { + _code.print(",\n " + c.getName() + "(id(\"" + + c.getName() + "\"))"); + } else if (c.getType().equals("wfifo")) { + System.out.println("Warning: YAPI visitor does not " + + "support windowed FIFOs."); + } + } + for (Process p : pn.getProcessList()) { + _code.print(",\n " + p.getName() + "(\"" + + p.getName() + "\", id(\"" + p.getName() + + "\")"); + + for (Port port : p.getPortList()) { + _code.print(", " + port.getPeerResource().getName()); + } + _code.print(")"); + } + _code.println(); + _code.println("{ }"); + _code.println(); + _code.println("const char* Application::type() const"); + _code.println("{"); + _code.println(" return \"Application\";"); + _code.println("}"); + } + catch (Exception e) { + System.out.println("YapiModuleVisitor: " + + "exception occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + protected CodePrintStream _code = null; + protected String _dir = null; + protected OutputStream _file; + protected CodePrintStream _pn; +} + diff --git a/dol/src/dol/visitor/yapi/YapiProcessVisitor.java b/dol/src/dol/visitor/yapi/YapiProcessVisitor.java new file mode 100644 index 0000000..660f540 --- /dev/null +++ b/dol/src/dol/visitor/yapi/YapiProcessVisitor.java @@ -0,0 +1,309 @@ +/* $Id: YapiProcessVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.yapi; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +import dol.datamodel.pn.Port; +import dol.datamodel.pn.Process; +import dol.datamodel.pn.ProcessNetwork; +import dol.datamodel.pn.SourceCode; +import dol.util.CodePrintStream; +import dol.util.Sed; +import dol.visitor.PNVisitor; + +/** + * + */ +public class YapiProcessVisitor extends PNVisitor { + + /** + * Constructor. + */ + public YapiProcessVisitor(String dir) { + _dir = dir; + } + + /** + * + * @param x process network that needs to be rendered + */ + public void visitComponent(ProcessNetwork x) { + try { + Vector pList = new Vector(); + + for (Process p : x.getProcessList()) { + String basename = p.getBasename(); + if (!pList.contains(basename)) { + pList.add(basename); + p.accept(this); + } + } + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * @param p process that needs to be rendered + */ + public void visitComponent(Process p) { + try { + _createCppFile(p); + _createHeaderFile(p); + _adaptSources(p); + } + catch (Exception e) { + System.out.println("Process Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + protected void _adaptSources(Process p) throws IOException { + Sed sed = new Sed(); + for (Port port : p.getPortList()) { + String processHeaderFile; + + for (SourceCode sr : p.getSrcList()) { + processHeaderFile = _dir + _delimiter + ".." + + _delimiter + "processes" + _delimiter + + sr.getLocality(). + replaceAll("(.*)\\.[cC][pP]*[pP]*", "$1\\.h"); + + if (port.isOutPort()) { + sed.sed(processHeaderFile, + "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?", + "$1 " + "(const_cast((const void*)" + +"(((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->OUTPORT_" + + port.getBasename() + "))))"); + } + else if (port.isInPort()) { + sed.sed(processHeaderFile, + "(#define[ ]*PORT_\\w*[ ]*)\"?" + + port.getBasename() + "\"?", + "$1 " + "(const_cast((const void*)" + +"(((static_cast<" + p.getBasename() + + "_wrapper *>(p->wptr))->INPORT_" + + port.getBasename() + "))))"); + } + } + } + } + + /** + * + */ + protected void _createCppFile(Process p) throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.cpp"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + String newline = System.getProperty("line.separator"); + String code = "#include \"" + p.getBasename() + "_wrapper.h\"" + + newline; + code += "#include \"dolSupport.h\"" + newline; + for (SourceCode sr : p.getSrcList()) { + code += "#include \"" + sr.getLocality() + "\"" + newline; + } + + code += newline; + + code += p.getBasename() + "_wrapper::" + p.getBasename() + + "_wrapper(const char* name, const Id& n"; + for (Port port : p.getPortList()) { + if (port.isInPort()) { + code += ", In& inport_" + port.getName(); + } else { + code += ", Out& outport_" + port.getName(); + } + } + code += ")" + newline; + + code += " : ProcessWrapper(name, n)" + newline; + code += " {" + newline; + code += " _state = (LocalState)new " + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State;" + + newline; + code += " _process.local = _state;" + newline; + code += " _process.init = " + p.getBasename() + "_init;" + + newline; + code += " _process.fire = " + p.getBasename() + "_fire;" + + newline; + code += " _process.wptr = this;" + newline; + for (Port port : p.getPortList()) { + if (!port.getRange().equals("")) { + String portName = + port.getName().replaceAll("_([0-9])", "][$1"); + portName += "]"; + portName = portName.replaceFirst("]", ""); + if (port.isOutPort()) { + code += " OUTPORT_" + portName + + " = new OutPort(id(\"" + port.getName() + + "\"), outport_" + port.getName() + ");" + + newline; + } + else if (port.isInPort()) { + code += " INPORT_" + portName + + " = new InPort(id(\"" + port.getName() + + "\"), inport_" + port.getName() + ");" + + newline; + } + } else { + if (port.isInPort()) { + code += " INPORT_" + port.getName() + + " = new InPort(id(\"" + port.getName() + + "\"), inport_" + port.getName() + ");" + + newline; + } else { + code += " OUTPORT_" + port.getName() + + " = new OutPort(id(\"" + port.getName() + + "\"), outport_" + port.getName() + ");" + + newline; + } + } + } + code += "}" + newline + newline; + + code += p.getBasename() + "_wrapper::~" + p.getBasename() + + "_wrapper() {" + newline; + code += " if (_state)" + newline; + code += " delete (" + + p.getBasename().substring(0, 1).toUpperCase() + + p.getBasename().substring(1) + "_State*)_state;" + + newline; + for (Port port : p.getPortList()) { + if (!port.getRange().equals("")) { + String portName = + port.getName().replaceAll("_([0-9])", "][$1"); + portName += "]"; + portName = portName.replaceFirst("]", ""); + if (port.isOutPort()) { + code += " if (OUTPORT_" + portName + ")" + newline; + code += " delete OUTPORT_" + portName + ";" + + newline; + } + else if (port.isInPort()) { + code += " if (INPORT_" + portName + ")" + newline; + code += " delete INPORT_" + portName + ";" + + newline; + } + } else { + if (port.isInPort()) { + code += " if(INPORT_" + port.getName() + ")" + + newline; + code += " delete INPORT_" + port.getName() + ";" + + newline; + } else { + code += " if(OUTPORT_" + port.getName() + ")" + + newline; + code += " delete OUTPORT_" + port.getName() + ";" + + newline; + } + } + } + code += "}" + newline + newline; + + code += "const char* " + + p.getBasename() + "_wrapper::type() const {" + newline; + code += " return \"" + p.getName() + "\";" + newline; + code += "}" + newline + newline; + + code += "void " + p.getBasename() + "_wrapper::main() {" + newline; + code += " _process.init(&_process);" + newline; + code += " while(!isDetached()) {" + newline; + code += " _process.fire(&_process);" + newline; + code += " }" + newline; + code += "}" + newline; + ps.printPrefixln(code); + } + + protected void _createHeaderFile(Process p) + throws IOException { + String filename = _dir + _delimiter + p.getBasename() + + "_wrapper.h"; + OutputStream file = new FileOutputStream(filename); + CodePrintStream ps = new CodePrintStream(file); + + String newline = System.getProperty("line.separator"); + String code = "#ifndef " + p.getBasename() + "_WRAPPER_H" + + newline; + code += "#define " + p.getBasename() + "_WRAPPER_H" + newline + + newline; + code += "#include \"ProcessWrapper.h\"" + newline; + code += newline; + + code += "class " + p.getBasename() + "_wrapper : " + + "public ProcessWrapper {" + newline; + code += " public:" + newline; + code += " " + p.getBasename() + + "_wrapper(const char* name, const Id& n"; + for (Port port : p.getPortList()) { + if (port.isInPort()) { + code += ", In& inport_" + port.getName(); + } else { + code += ", Out& outport_" + port.getName(); + } + } + code += ");" + newline; + code += " virtual ~" + p.getBasename() + "_wrapper();" + + newline; + code += " const char* type() const;" + newline; + code += " void main();" + newline + newline; + + Vector portList = new Vector(); + for (Port port : p.getPortList()) { + String basename = port.getBasename(); + + if (!portList.contains(basename)) { + portList.add(basename); + + if (!port.getRange().equals("")) { + if (port.isOutPort()) { + code += " OutPort *OUTPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];" + newline; + } + else if (port.isInPort()) { + code += " InPort *INPORT_" + + port.getBasename() + "[" + + port.getRange().replaceAll( + ";", "\\]\\[") + "];" + newline; + } + } + else { + if (port.isOutPort()) { + code += " OutPort *OUTPORT_" + + port.getName() + ";" + newline; + } + else if (port.isInPort()) { + code += " InPort *INPORT_" + + port.getName() + ";" + newline; + } + } + } + } + code += newline; + code += " protected:" + newline; + code += " LocalState _state;" + newline; + code += "};" + newline + newline; + code += "#endif"; + ps.printPrefixln(code); + } + + protected String _dir = null; +} diff --git a/dol/src/dol/visitor/yapi/YapiVisitor.java b/dol/src/dol/visitor/yapi/YapiVisitor.java new file mode 100644 index 0000000..3815727 --- /dev/null +++ b/dol/src/dol/visitor/yapi/YapiVisitor.java @@ -0,0 +1,95 @@ +/* $Id: YapiVisitor.java 1 2010-02-24 13:03:05Z haidw $ */ +package dol.visitor.yapi; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import dol.datamodel.pn.ProcessNetwork; +import dol.util.Copier; +import dol.visitor.PNVisitor; + +/** + * + */ +public class YapiVisitor extends PNVisitor { + + /** + * Constructor. + */ + public YapiVisitor(String packageName) { + _packageName = packageName; + } + + /** + * + */ + public void visitComponent(ProcessNetwork pn) { + try { + _generateDirHierarchy(); + + pn.accept(new YapiMakefileVisitor(_srcDir)); + pn.accept(new YapiModuleVisitor(_srcDir)); + pn.accept(new YapiProcessVisitor(_wrapperDir)); + + } catch (Exception e) { + System.out.println(" SystemC PN Visitor: exception " + + "occured: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + */ + private void _generateDirHierarchy() + throws IOException, FileNotFoundException { + + File dir = new File(_packageName); + dir.mkdirs(); + + _srcDir = _packageName + _delimiter + _srcDirName; + dir = new File(_srcDir); + dir.mkdirs(); + + _libDir = _srcDir + _delimiter + _libDirName; + dir = new File(_libDir); + dir.mkdirs(); + + _processDir = _srcDir + _delimiter + _processDirName; + dir = new File(_processDir); + dir.mkdirs(); + + _wrapperDir = _srcDir + _delimiter + _wrapperDirName; + dir = new File(_wrapperDir); + dir.mkdirs(); + + // copy library + String libraryPath = _ui.getMySystemCLib(); + libraryPath = libraryPath.replaceAll("systemC", + "yapi"); + File source = new File(libraryPath); + File destination = new File(_libDir); + new Copier().copy(source, destination); + + //copy process src code + source = new File(_srcDirName); + destination = new File(_processDir); + new Copier().copy(source, destination); + } + + protected String _packageName = null; + + protected String _srcDir = ""; + protected static String _srcDirName = "src"; + + protected String _libDir = ""; + protected static String _libDirName = "lib"; + + protected String _processDir = ""; + protected static String _processDirName = "processes"; + + protected String _wrapperDir = ""; + protected static String _wrapperDirName = "wrappers"; +} + diff --git a/dol/src/dol/visitor/yapi/lib/ProcessWrapper.cpp b/dol/src/dol/visitor/yapi/lib/ProcessWrapper.cpp new file mode 100644 index 0000000..de28efe --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/ProcessWrapper.cpp @@ -0,0 +1,107 @@ +#include "ProcessWrapper.h" +#include "dolSupport.h" + +/** + * + */ +ProcessWrapper::ProcessWrapper(const char* name, const Id& n) : Process(n) { + _name = new char[strlen(name) + 1]; + strcpy(_name, name); + + _isDetached = false; + for (int i = 0; i < 4; i++) { + _iteratorIndex[i] = getIndex(_name, "_", i); + } +} + +/** + * + */ +ProcessWrapper::~ProcessWrapper() { +} + +/** + * + */ +void ProcessWrapper::initialize() { + _process.init(&_process); +} + +/** + * + */ +int ProcessWrapper::fire() +{ + return _process.fire(&_process); +} + + +/** + * + */ +void ProcessWrapper::detach() { + _isDetached = true; +} + + +/** + * Gets an index of a string, where the index must be separated by + * a character specified in tokens. + * Returns -1, when an error occurs. + * + * Example: + * getIndex("name_1_2", "_", 0) will return 1. + * getIndex("name_1_2", "_", 1) will return 2. + * + * @param string string to parse + * @param tokens delimiter of indices + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(const char* string, char* tokens, + int indexNumber) const { + char* string_copy; + char* token_pointer; + int index = 0; + + string_copy = (char*) malloc(sizeof(char) * (strlen(string) + 1)); + if (!string_copy) { + fprintf(stderr, "getIndex(): could not allocate memory.\n"); + return -1; + } + + strcpy(string_copy, string); + + token_pointer = strtok(string_copy, tokens); + do { + token_pointer = strtok(NULL, tokens); + index++; + } while (index <= indexNumber && token_pointer != 0); + + if (token_pointer) { + index = atoi(token_pointer); + free(string_copy); + return index; + } + + return -1; +} + + +/** + * Get the name of this process. + */ +char* ProcessWrapper::getName() const { + return _name; +} + + +/** + * Get the index of this process. + * @param indexNumber position of index (starting at 0) + */ +int ProcessWrapper::getIndex(unsigned indexNumber) const { + if (indexNumber < 4) { + return _iteratorIndex[indexNumber]; + } + return -1; +} diff --git a/dol/src/dol/visitor/yapi/lib/ProcessWrapper.h b/dol/src/dol/visitor/yapi/lib/ProcessWrapper.h new file mode 100644 index 0000000..bdecd34 --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/ProcessWrapper.h @@ -0,0 +1,29 @@ +#ifndef _PROCESSWRAPPER_H_ +#define _PROCESSWRAPPER_H_ + +#include "dol.h" +#include "yapi.h" + +class ProcessWrapper : public Process +{ + public: + ProcessWrapper(const char* name, const Id& n); + virtual ~ProcessWrapper(); + virtual void initialize(); + virtual int fire(); + virtual bool isDetached() const { return _isDetached; } + virtual void detach(); + virtual int getIndex(unsigned indexNumber) const; + virtual char* getName() const; + + protected: + char* _name; + DOLProcess _process; + bool _isDetached; + int _iteratorIndex[4]; + virtual int getIndex(const char* string, char* tokens, + int indexNumber) const; +}; + +#endif + diff --git a/dol/src/dol/visitor/yapi/lib/dol.h b/dol/src/dol/visitor/yapi/lib/dol.h new file mode 100644 index 0000000..8fbefe4 --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/dol.h @@ -0,0 +1,39 @@ +#ifndef DOL_H +#define DOL_H + +/************************************************************************ + * do not add code to this header + ************************************************************************/ + +/** + * Define the DOL process handler scheme. + * - Local variables are defined in structure LocalState. Local + * variables may vary from different processes. + * - The ProcessInit function pointer points to a function which + * initializes a process. + * - The ProcessFire function pointer points to a function which + * performs the actual computation. The communication between + * processes is inside the ProcessFire function. + * - The WPTR is a placeholder for callback. One can just + * leave it blank. + */ + +//structure for local memory of process +typedef struct _local_states *LocalState; + +//additional behavioral functions could be declared here +typedef void (*ProcessInit)(struct _process*); +typedef int (*ProcessFire)(struct _process*); +typedef void *WPTR; + +//process handler +struct _process; + +typedef struct _process { + LocalState local; + ProcessInit init; + ProcessFire fire; + WPTR wptr; //placeholder for wrapper instance +} DOLProcess; + +#endif diff --git a/dol/src/dol/visitor/yapi/lib/dolSupport.cpp b/dol/src/dol/visitor/yapi/lib/dolSupport.cpp new file mode 100644 index 0000000..164e1a6 --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/dolSupport.cpp @@ -0,0 +1,81 @@ +#include "dolSupport.h" +#include "ProcessWrapper.h" + +/** + * + */ +unsigned dolwrite(OutPort *out, const void *buf, unsigned len, const DOLProcess *process) +{ + write(*out, (char*)buf, len); + return len; +} + + +/** + * + */ +unsigned dolread(InPort *in, const void *buf, unsigned len, const DOLProcess *process) { + read(*in, (char*)buf, len); + return len; +} + +/** + * + */ +void DOL_detach(DOLProcess* p) { + static_cast(p->wptr)->detach(); +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0) { + *port = (void**)((void**)base)[index0]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1) { + *port = (void**)((void**)base)[index0 * range1 + index1]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2) { + *port = (void**)((void**)base)[index0 * range1 * range2 + + index1 * range2 + index2]; +} + + +/** + * + */ +void createPort(void** port, + void* base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3) { + *port = (void**)((void**)base)[index0 * range1 * range2 * range3 + + index1 * range2 * range3 + + index2 * range3 + + index3]; +} diff --git a/dol/src/dol/visitor/yapi/lib/dolSupport.h b/dol/src/dol/visitor/yapi/lib/dolSupport.h new file mode 100644 index 0000000..897516f --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/dolSupport.h @@ -0,0 +1,67 @@ +#ifndef DOLSUPPORT_H +#define DOLSUPPORT_H + +#include "dol.h" +#include "yapi.h" + +#define DOL_write(port, buf, len, process) \ + dolwrite(static_cast *>(port), buf, len, process) +#define DOL_read(port, buf, len, process) \ + dolread(static_cast *>(port), buf, len, process) + +void DOL_detach(DOLProcess* p); + +//fifo access functions +unsigned dolwrite(OutPort *out, const void *buf, unsigned len, const DOLProcess *process); +unsigned dolread(InPort *in, const void *buf, unsigned len, const DOLProcess *process); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2); + +void createPort(void **port, + void *base, + int number_of_indices, + int index0, int range0, + int index1, int range1, + int index2, int range2, + int index3, int range3); + +#define GETINDEX(dimension) \ + static_cast(p->wptr)->getIndex(dimension) + +/** + * macro to create a variable to store a port name + * + * @param name name of the variable + */ +#define CREATEPORTVAR(name) void *name + +/** + * Create the port name of an iterated port based on its basename and the + * given indices. + * + * @param port buffer where the result is stored (created using + * CREATEPORTVAR) + * @param base basename of the port + * @param number_of_indices number of dimensions of the port + * @param index_range_pairs index and range values for each dimension + */ +#define CREATEPORT(port, base, number_of_indices, index_range_pairs...) \ + createPort((void**)(&port), base, number_of_indices, index_range_pairs) + +#endif diff --git a/dol/src/dol/visitor/yapi/lib/main.cpp b/dol/src/dol/visitor/yapi/lib/main.cpp new file mode 100644 index 0000000..2879eb3 --- /dev/null +++ b/dol/src/dol/visitor/yapi/lib/main.cpp @@ -0,0 +1,41 @@ +#include "yapi.h" +#include "application.h" +#include + +using namespace std; + +int main() +{ + // create yapi run-time environment + RTE rte; + + // redirect standard output + ofstream f("./app.out"); + rte.setOutStream(f); + + // redirect standard error + ofstream g("./app.err"); + rte.setErrorStream(g); + + // create toplevel process network + Application app(id("app")); + + // start the process network and + // wait for processes to finish + rte.start(app); + + // print workload + ofstream h("./appworkload.txt"); + printWorkload(app, h); + + // generate dotty file + ofstream i("./app.dot"); + printDotty(app, i); + + f.close(); + g.close(); + h.close(); + i.close(); + + return 0; +} diff --git a/dol/src/dol/visitor/yapi/package.html b/dol/src/dol/visitor/yapi/package.html new file mode 100644 index 0000000..813bcf2 --- /dev/null +++ b/dol/src/dol/visitor/yapi/package.html @@ -0,0 +1,20 @@ + + + + + + +Code generator for YAPI functional simulation. + +

Package Specification

+ + + +

Related Documentation

+ + + + + + + diff --git a/dol/src/dol_template.properties b/dol/src/dol_template.properties new file mode 100644 index 0000000..85d8e36 --- /dev/null +++ b/dol/src/dol_template.properties @@ -0,0 +1,23 @@ +# template for dol.properties file +# use ant config to fill in the values + +# DOL path: +# path to local directory of DOL +# +# example: +# DOL_PATH = /home/shapes/dol + +DOL_path = @dol_path@ + +# SystemC library path: +# SYSTEMC_INC and SYSTEMC_LIB are the paths to SystemC header and library, +# respectively. These two path will be used in the generated +# Makefile to build the SystemC executable binary file. +# They have to point to local SystemC directory. +# +# example: +# SYSTEMC_INC = /home/shapes/base/resources/lib/systemC/systemc-2.1.v1/include +# SYSTEMC_LIB = /home/shapes/base/resources/lib/systemC/systemc-2.1.v1/lib-linux/libsystemc.a + +SYSTEMC_INC = @systemc_inc@ +SYSTEMC_LIB = @systemc_lib@ diff --git a/dol/test/dolziptest.xml b/dol/test/dolziptest.xml new file mode 100644 index 0000000..2c6d3e7 --- /dev/null +++ b/dol/test/dolziptest.xml @@ -0,0 +1,78 @@ + + + + + + Ant build file for automated testing of DOL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/test/reference/example1_Linux.txt b/dol/test/reference/example1_Linux.txt new file mode 100644 index 0000000..28e844f --- /dev/null +++ b/dol/test/reference/example1_Linux.txt @@ -0,0 +1,20 @@ +consumer: 0.000000 +consumer: 1.000000 +consumer: 4.000000 +consumer: 9.000000 +consumer: 16.000000 +consumer: 25.000000 +consumer: 36.000000 +consumer: 49.000000 +consumer: 64.000000 +consumer: 81.000000 +consumer: 100.000000 +consumer: 121.000000 +consumer: 144.000000 +consumer: 169.000000 +consumer: 196.000000 +consumer: 225.000000 +consumer: 256.000000 +consumer: 289.000000 +consumer: 324.000000 +consumer: 361.000000 diff --git a/dol/test/reference/example1_Windows XP.txt b/dol/test/reference/example1_Windows XP.txt new file mode 100644 index 0000000..28e844f --- /dev/null +++ b/dol/test/reference/example1_Windows XP.txt @@ -0,0 +1,20 @@ +consumer: 0.000000 +consumer: 1.000000 +consumer: 4.000000 +consumer: 9.000000 +consumer: 16.000000 +consumer: 25.000000 +consumer: 36.000000 +consumer: 49.000000 +consumer: 64.000000 +consumer: 81.000000 +consumer: 100.000000 +consumer: 121.000000 +consumer: 144.000000 +consumer: 169.000000 +consumer: 196.000000 +consumer: 225.000000 +consumer: 256.000000 +consumer: 289.000000 +consumer: 324.000000 +consumer: 361.000000 diff --git a/dol/test/reference/example2_Linux.txt b/dol/test/reference/example2_Linux.txt new file mode 100644 index 0000000..6db8b1c --- /dev/null +++ b/dol/test/reference/example2_Linux.txt @@ -0,0 +1,20 @@ +consumer: 0.000000 +consumer: 1.000000 +consumer: 256.000000 +consumer: 6561.000000 +consumer: 65536.000000 +consumer: 390625.000000 +consumer: 1679616.000000 +consumer: 5764801.000000 +consumer: 16777216.000000 +consumer: 43046720.000000 +consumer: 100000000.000000 +consumer: 214358880.000000 +consumer: 429981696.000000 +consumer: 815730752.000000 +consumer: 1475789056.000000 +consumer: 2562890752.000000 +consumer: 4294967296.000000 +consumer: 6975757312.000000 +consumer: 11019960320.000000 +consumer: 16983563264.000000 diff --git a/dol/test/reference/example2_Windows XP.txt b/dol/test/reference/example2_Windows XP.txt new file mode 100644 index 0000000..6db8b1c --- /dev/null +++ b/dol/test/reference/example2_Windows XP.txt @@ -0,0 +1,20 @@ +consumer: 0.000000 +consumer: 1.000000 +consumer: 256.000000 +consumer: 6561.000000 +consumer: 65536.000000 +consumer: 390625.000000 +consumer: 1679616.000000 +consumer: 5764801.000000 +consumer: 16777216.000000 +consumer: 43046720.000000 +consumer: 100000000.000000 +consumer: 214358880.000000 +consumer: 429981696.000000 +consumer: 815730752.000000 +consumer: 1475789056.000000 +consumer: 2562890752.000000 +consumer: 4294967296.000000 +consumer: 6975757312.000000 +consumer: 11019960320.000000 +consumer: 16983563264.000000 diff --git a/dol/test/reference/example3_Linux.txt b/dol/test/reference/example3_Linux.txt new file mode 100644 index 0000000..975ef62 --- /dev/null +++ b/dol/test/reference/example3_Linux.txt @@ -0,0 +1,78 @@ +v_consumer: a +v_consumer: a +h_consumer: n +h_consumer: n +h_consumer: n +v_consumer: a +v_consumer: b +v_consumer: b +h_consumer: o +h_consumer: o +h_consumer: o +v_consumer: b +v_consumer: c +v_consumer: c +h_consumer: p +h_consumer: p +h_consumer: p +v_consumer: c +v_consumer: d +v_consumer: d +h_consumer: q +h_consumer: q +h_consumer: q +v_consumer: d +v_consumer: e +v_consumer: e +h_consumer: r +h_consumer: r +h_consumer: r +v_consumer: e +v_consumer: f +v_consumer: f +h_consumer: s +h_consumer: s +h_consumer: s +v_consumer: f +v_consumer: g +v_consumer: g +h_consumer: t +h_consumer: t +h_consumer: t +v_consumer: g +v_consumer: h +v_consumer: h +h_consumer: u +h_consumer: u +h_consumer: u +v_consumer: h +v_consumer: i +v_consumer: i +h_consumer: v +h_consumer: v +h_consumer: v +v_consumer: i +v_consumer: j +v_consumer: j +h_consumer: w +h_consumer: w +h_consumer: w +v_consumer: j +v_consumer: k +v_consumer: k +h_consumer: x +h_consumer: x +h_consumer: x +v_consumer: k +v_consumer: l +v_consumer: l +h_consumer: y +h_consumer: y +h_consumer: y +v_consumer: l +v_consumer: m +v_consumer: m +h_consumer: z +h_consumer: z +h_consumer: z +v_consumer: m diff --git a/dol/test/reference/example3_Windows XP.txt b/dol/test/reference/example3_Windows XP.txt new file mode 100644 index 0000000..975ef62 --- /dev/null +++ b/dol/test/reference/example3_Windows XP.txt @@ -0,0 +1,78 @@ +v_consumer: a +v_consumer: a +h_consumer: n +h_consumer: n +h_consumer: n +v_consumer: a +v_consumer: b +v_consumer: b +h_consumer: o +h_consumer: o +h_consumer: o +v_consumer: b +v_consumer: c +v_consumer: c +h_consumer: p +h_consumer: p +h_consumer: p +v_consumer: c +v_consumer: d +v_consumer: d +h_consumer: q +h_consumer: q +h_consumer: q +v_consumer: d +v_consumer: e +v_consumer: e +h_consumer: r +h_consumer: r +h_consumer: r +v_consumer: e +v_consumer: f +v_consumer: f +h_consumer: s +h_consumer: s +h_consumer: s +v_consumer: f +v_consumer: g +v_consumer: g +h_consumer: t +h_consumer: t +h_consumer: t +v_consumer: g +v_consumer: h +v_consumer: h +h_consumer: u +h_consumer: u +h_consumer: u +v_consumer: h +v_consumer: i +v_consumer: i +h_consumer: v +h_consumer: v +h_consumer: v +v_consumer: i +v_consumer: j +v_consumer: j +h_consumer: w +h_consumer: w +h_consumer: w +v_consumer: j +v_consumer: k +v_consumer: k +h_consumer: x +h_consumer: x +h_consumer: x +v_consumer: k +v_consumer: l +v_consumer: l +h_consumer: y +h_consumer: y +h_consumer: y +v_consumer: l +v_consumer: m +v_consumer: m +h_consumer: z +h_consumer: z +h_consumer: z +v_consumer: m diff --git a/dol/test/reference/example4_Linux.txt b/dol/test/reference/example4_Linux.txt new file mode 100644 index 0000000..3a7079b --- /dev/null +++ b/dol/test/reference/example4_Linux.txt @@ -0,0 +1,32 @@ +input_generator: Write to zeroinput_0: 0.000000 +input_generator: Write to matrixA_0_0_0: 1.000000 +input_generator: Write to matrixB_0_0_0: 0.000000 +input_generator: Write to matrixA_1_0_0: 1.000000 +input_generator: Write to matrixB_1_0_0: 0.000000 +input_generator: Write to zeroinput_1: 0.000000 +input_generator: Write to matrixA_0_0_1: 2.000000 +input_generator: Write to matrixB_0_0_1: -1.000000 +input_generator: Write to matrixA_1_0_1: 2.000000 +input_generator: Write to matrixB_1_0_1: -1.000000 +input_generator: Write to zeroinput_2: 0.000000 +input_generator: Write to matrixA_0_1_0: 3.000000 +input_generator: Write to matrixB_0_1_0: -2.000000 +input_generator: Write to matrixA_1_1_0: 3.000000 +input_generator: Write to matrixB_1_1_0: -2.000000 +input_generator: Write to zeroinput_3: 0.000000 +input_generator: Write to matrixA_0_1_1: 4.000000 +input_generator: Write to matrixB_0_1_1: -3.000000 +input_generator: Write to matrixA_1_1_1: 4.000000 +input_generator: Write to matrixB_1_1_1: -3.000000 + addmult_0_0_0: 1.000000 * 0.000000 + 0.000000 = 0.000000 + addmult_0_0_1: 2.000000 * -2.000000 + 0.000000 = -4.000000 + addmult_0_1_0: 3.000000 * 0.000000 + 0.000000 = 0.000000 + addmult_0_1_1: 4.000000 * -2.000000 + 0.000000 = -8.000000 + addmult_1_0_0: 1.000000 * -1.000000 + 0.000000 = -1.000000 + addmult_1_0_1: 2.000000 * -3.000000 + -1.000000 = -7.000000 + addmult_1_1_0: 3.000000 * -1.000000 + 0.000000 = -3.000000 + addmult_1_1_1: 4.000000 * -3.000000 + -3.000000 = -15.000000 +output_consumer: matrixC[0][0]: -4.000000 +output_consumer: matrixC[0][1]: -7.000000 +output_consumer: matrixC[1][0]: -8.000000 +output_consumer: matrixC[1][1]: -15.000000 diff --git a/dol/test/reference/example4_Windows XP.txt b/dol/test/reference/example4_Windows XP.txt new file mode 100644 index 0000000..3a7079b --- /dev/null +++ b/dol/test/reference/example4_Windows XP.txt @@ -0,0 +1,32 @@ +input_generator: Write to zeroinput_0: 0.000000 +input_generator: Write to matrixA_0_0_0: 1.000000 +input_generator: Write to matrixB_0_0_0: 0.000000 +input_generator: Write to matrixA_1_0_0: 1.000000 +input_generator: Write to matrixB_1_0_0: 0.000000 +input_generator: Write to zeroinput_1: 0.000000 +input_generator: Write to matrixA_0_0_1: 2.000000 +input_generator: Write to matrixB_0_0_1: -1.000000 +input_generator: Write to matrixA_1_0_1: 2.000000 +input_generator: Write to matrixB_1_0_1: -1.000000 +input_generator: Write to zeroinput_2: 0.000000 +input_generator: Write to matrixA_0_1_0: 3.000000 +input_generator: Write to matrixB_0_1_0: -2.000000 +input_generator: Write to matrixA_1_1_0: 3.000000 +input_generator: Write to matrixB_1_1_0: -2.000000 +input_generator: Write to zeroinput_3: 0.000000 +input_generator: Write to matrixA_0_1_1: 4.000000 +input_generator: Write to matrixB_0_1_1: -3.000000 +input_generator: Write to matrixA_1_1_1: 4.000000 +input_generator: Write to matrixB_1_1_1: -3.000000 + addmult_0_0_0: 1.000000 * 0.000000 + 0.000000 = 0.000000 + addmult_0_0_1: 2.000000 * -2.000000 + 0.000000 = -4.000000 + addmult_0_1_0: 3.000000 * 0.000000 + 0.000000 = 0.000000 + addmult_0_1_1: 4.000000 * -2.000000 + 0.000000 = -8.000000 + addmult_1_0_0: 1.000000 * -1.000000 + 0.000000 = -1.000000 + addmult_1_0_1: 2.000000 * -3.000000 + -1.000000 = -7.000000 + addmult_1_1_0: 3.000000 * -1.000000 + 0.000000 = -3.000000 + addmult_1_1_1: 4.000000 * -3.000000 + -3.000000 = -15.000000 +output_consumer: matrixC[0][0]: -4.000000 +output_consumer: matrixC[0][1]: -7.000000 +output_consumer: matrixC[1][0]: -8.000000 +output_consumer: matrixC[1][1]: -15.000000 diff --git a/dol/test/reference/example5_Linux.txt b/dol/test/reference/example5_Linux.txt new file mode 100644 index 0000000..3e36a91 --- /dev/null +++ b/dol/test/reference/example5_Linux.txt @@ -0,0 +1,64 @@ + FFT2_0_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_1: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_2: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_3: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_5: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_6: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_7: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_1: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_2: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_3: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_5: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_6: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_7: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_2_1: twiddle_factor 0.707107 + j * -0.707107 + FFT2_2_2: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_3: twiddle_factor -0.707107 + j * -0.707107 + FFT2_2_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_2_5: twiddle_factor 0.707107 + j * -0.707107 + FFT2_2_6: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_7: twiddle_factor -0.707107 + j * -0.707107 + FFT2_3_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_3_1: twiddle_factor 0.923880 + j * -0.382683 + FFT2_3_2: twiddle_factor 0.707107 + j * -0.707107 + FFT2_3_3: twiddle_factor 0.382683 + j * -0.923880 + FFT2_3_4: twiddle_factor -0.000000 + j * -1.000000 + FFT2_3_5: twiddle_factor -0.382684 + j * -0.923880 + FFT2_3_6: twiddle_factor -0.707107 + j * -0.707107 + FFT2_3_7: twiddle_factor -0.923880 + j * -0.382683 +input_generator: Write to input_coefficients_0: -7.000000 + j * 6.000000 +input_generator: Write to input_coefficients_1: -6.000000 + j * -6.000000 +input_generator: Write to input_coefficients_2: 1.000000 + j * -1.000000 +input_generator: Write to input_coefficients_3: 1.000000 + j * 2.000000 +input_generator: Write to input_coefficients_4: -7.000000 + j * -2.000000 +input_generator: Write to input_coefficients_5: -8.000000 + j * 0.000000 +input_generator: Write to input_coefficients_6: -8.000000 + j * 1.000000 +input_generator: Write to input_coefficients_7: -8.000000 + j * 1.000000 +input_generator: Write to input_coefficients_8: -2.000000 + j * 8.000000 +input_generator: Write to input_coefficients_9: -4.000000 + j * 0.000000 +input_generator: Write to input_coefficients_10: 1.000000 + j * 6.000000 +input_generator: Write to input_coefficients_11: -1.000000 + j * 3.000000 +input_generator: Write to input_coefficients_12: -2.000000 + j * 3.000000 +input_generator: Write to input_coefficients_13: -3.000000 + j * -2.000000 +input_generator: Write to input_coefficients_14: -4.000000 + j * -8.000000 +input_generator: Write to input_coefficients_15: -3.000000 + j * -5.000000 +output_consumer: coeff[0]: -60.000000 + j * 6.000000 +output_consumer: coeff[1]: 0.616942 + j * -12.269464 +output_consumer: coeff[2]: 8.464464 + j * -18.677670 +output_consumer: coeff[3]: -7.116004 + j * 10.640678 +output_consumer: coeff[4]: -17.000000 + j * 27.000000 +output_consumer: coeff[5]: -9.306217 + j * 18.624950 +output_consumer: coeff[6]: -1.393398 + j * 27.707106 +output_consumer: coeff[7]: -3.112326 + j * -3.457108 +output_consumer: coeff[8]: 4.000000 + j * 20.000000 +output_consumer: coeff[9]: -12.131660 + j * 1.298900 +output_consumer: coeff[10]: 15.535534 + j * 16.677666 +output_consumer: coeff[11]: 4.287576 + j * 3.643592 +output_consumer: coeff[12]: 1.000000 + j * 7.000000 +output_consumer: coeff[13]: -19.179064 + j * 4.345614 +output_consumer: coeff[14]: -22.606600 + j * 26.292894 +output_consumer: coeff[15]: 5.940753 + j * -38.827164 diff --git a/dol/test/reference/example5_Windows XP.txt b/dol/test/reference/example5_Windows XP.txt new file mode 100644 index 0000000..dfe7b6f --- /dev/null +++ b/dol/test/reference/example5_Windows XP.txt @@ -0,0 +1,64 @@ + FFT2_0_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_1: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_2: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_3: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_5: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_6: twiddle_factor 1.000000 + j * -0.000000 + FFT2_0_7: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_1: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_2: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_3: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_5: twiddle_factor -0.000000 + j * -1.000000 + FFT2_1_6: twiddle_factor 1.000000 + j * -0.000000 + FFT2_1_7: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_2_1: twiddle_factor 0.707107 + j * -0.707107 + FFT2_2_2: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_3: twiddle_factor -0.707107 + j * -0.707107 + FFT2_2_4: twiddle_factor 1.000000 + j * -0.000000 + FFT2_2_5: twiddle_factor 0.707107 + j * -0.707107 + FFT2_2_6: twiddle_factor -0.000000 + j * -1.000000 + FFT2_2_7: twiddle_factor -0.707107 + j * -0.707107 + FFT2_3_0: twiddle_factor 1.000000 + j * -0.000000 + FFT2_3_1: twiddle_factor 0.923880 + j * -0.382683 + FFT2_3_2: twiddle_factor 0.707107 + j * -0.707107 + FFT2_3_3: twiddle_factor 0.382683 + j * -0.923880 + FFT2_3_4: twiddle_factor -0.000000 + j * -1.000000 + FFT2_3_5: twiddle_factor -0.382683 + j * -0.923880 + FFT2_3_6: twiddle_factor -0.707107 + j * -0.707107 + FFT2_3_7: twiddle_factor -0.923880 + j * -0.382683 +input_generator: Write to input_coefficients_0: -9.000000 + j * 4.000000 +input_generator: Write to input_coefficients_1: -2.000000 + j * 0.000000 +input_generator: Write to input_coefficients_2: -3.000000 + j * 0.000000 +input_generator: Write to input_coefficients_3: 8.000000 + j * 3.000000 +input_generator: Write to input_coefficients_4: 6.000000 + j * 0.000000 +input_generator: Write to input_coefficients_5: -7.000000 + j * -9.000000 +input_generator: Write to input_coefficients_6: -5.000000 + j * 2.000000 +input_generator: Write to input_coefficients_7: 1.000000 + j * 2.000000 +input_generator: Write to input_coefficients_8: 8.000000 + j * -4.000000 +input_generator: Write to input_coefficients_9: -6.000000 + j * -4.000000 +input_generator: Write to input_coefficients_10: 9.000000 + j * -2.000000 +input_generator: Write to input_coefficients_11: -8.000000 + j * -4.000000 +input_generator: Write to input_coefficients_12: -3.000000 + j * 1.000000 +input_generator: Write to input_coefficients_13: -9.000000 + j * 7.000000 +input_generator: Write to input_coefficients_14: -9.000000 + j * -9.000000 +input_generator: Write to input_coefficients_15: -7.000000 + j * 7.000000 +output_consumer: coeff[0]: -36.000000 + j * -6.000000 +output_consumer: coeff[1]: -27.156870 + j * -5.812504 +output_consumer: coeff[2]: -6.071069 + j * -25.242640 +output_consumer: coeff[3]: -7.438680 + j * 7.017483 +output_consumer: coeff[4]: -3.999999 + j * 28.000000 +output_consumer: coeff[5]: 1.106642 + j * 13.313931 +output_consumer: coeff[6]: -18.899494 + j * 3.443651 +output_consumer: coeff[7]: -46.865753 + j * -33.359482 +output_consumer: coeff[8]: 24.000000 + j * -10.000000 +output_consumer: coeff[9]: -13.085772 + j * 2.398290 +output_consumer: coeff[10]: 8.071067 + j * -16.757360 +output_consumer: coeff[11]: 16.450871 + j * 51.024147 +output_consumer: coeff[12]: 24.000000 + j * -8.000000 +output_consumer: coeff[13]: -32.863998 + j * -13.899717 +output_consumer: coeff[14]: 0.899496 + j * 34.556351 +output_consumer: coeff[15]: -26.146439 + j * 43.317848 diff --git a/dol/test/reference/example6_Linux.txt b/dol/test/reference/example6_Linux.txt new file mode 100644 index 0000000..aef2f51 --- /dev/null +++ b/dol/test/reference/example6_Linux.txt @@ -0,0 +1,21 @@ +producer: samples = { -7.0, +6.0, -6.0, -6.0, +1.0, -1.0, +1.0, +2.0, -7.0, -2.0 } +producer: Write sample[00]: -7.0000 +producer: Write sample[01]: +6.0000 +producer: Write sample[02]: -6.0000 +consumer: Read sample[00]: -7.0000 +producer: Write sample[03]: -6.0000 +consumer: Read sample[01]: +2.5000 +producer: Write sample[04]: +1.0000 +consumer: Read sample[02]: -4.7500 +producer: Write sample[05]: -1.0000 +consumer: Read sample[03]: -8.3750 +producer: Write sample[06]: +1.0000 +consumer: Read sample[04]: -3.1875 +producer: Write sample[07]: +2.0000 +consumer: Read sample[05]: -2.5938 +producer: Write sample[08]: -7.0000 +consumer: Read sample[06]: -0.2969 +producer: Write sample[09]: -2.0000 +consumer: Read sample[07]: +1.8516 +consumer: Read sample[08]: -6.0742 +consumer: Read sample[09]: -5.0371 diff --git a/dol/test/reference/example6_Windows XP.txt b/dol/test/reference/example6_Windows XP.txt new file mode 100644 index 0000000..d266e3c --- /dev/null +++ b/dol/test/reference/example6_Windows XP.txt @@ -0,0 +1,21 @@ +producer: samples = { -9.0, +4.0, -2.0, +0.0, -3.0, +0.0, +8.0, +3.0, +6.0, +0.0 } +producer: Write sample[00]: -9.0000 +producer: Write sample[01]: +4.0000 +producer: Write sample[02]: -2.0000 +consumer: Read sample[00]: -9.0000 +producer: Write sample[03]: +0.0000 +consumer: Read sample[01]: -0.5000 +producer: Write sample[04]: -3.0000 +consumer: Read sample[02]: -2.2500 +producer: Write sample[05]: +0.0000 +consumer: Read sample[03]: -1.1250 +producer: Write sample[06]: +8.0000 +consumer: Read sample[04]: -3.5625 +producer: Write sample[07]: +3.0000 +consumer: Read sample[05]: -1.7812 +producer: Write sample[08]: +6.0000 +consumer: Read sample[06]: +7.1094 +producer: Write sample[09]: +0.0000 +consumer: Read sample[07]: +6.5547 +consumer: Read sample[08]: +9.2773 +consumer: Read sample[09]: +4.6387 diff --git a/dol/test/reference/example7_Linux.txt b/dol/test/reference/example7_Linux.txt new file mode 100644 index 0000000..3639ade --- /dev/null +++ b/dol/test/reference/example7_Linux.txt @@ -0,0 +1,31 @@ +init producer. +init consumer. +init filter_0: filter coefficient = -0.9 +init filter_1: filter coefficient = -0.6 +init filter_2: filter coefficient = -0.1 +producer: samples = { -7.0, +6.0, -6.0, -6.0, +1.0 } +producer: Write sample[00]: -7.0000 +producer: Write sample[01]: +6.0000 +filter_0: inA: -7.0000, inB: 0.0000, outA = outB: -7.0000 +filter_1: inA: -7.0000, inB: 0.0000, outA: -7.0000, outB: 4.2000 +filter_2: inA: -7.0000, inB: 0.0000, outA: -7.0000, outB: 0.7000 +producer: Write sample[02]: -6.0000 +consumer: Read sample[00]: -7.0000 +filter_0: inA: 6.0000, inB: 4.2000, outA = outB: 10.2000 +filter_1: inA: 10.2000, inB: 0.7000, outA: 10.2000, outB: -5.4200 +filter_2: inA: 10.2000, inB: 0.0000, outA: 10.2000, outB: -1.0200 +producer: Write sample[03]: -6.0000 +consumer: Read sample[01]: +10.2000 +filter_0: inA: -6.0000, inB: -5.4200, outA = outB: -11.4200 +filter_1: inA: -11.4200, inB: -1.0200, outA: -11.4200, outB: 5.8320 +filter_2: inA: -11.4200, inB: 0.0000, outA: -11.4200, outB: 1.1420 +producer: Write sample[04]: +1.0000 +consumer: Read sample[02]: -11.4200 +filter_0: inA: -6.0000, inB: 5.8320, outA = outB: -0.1680 +filter_1: inA: -0.1680, inB: 1.1420, outA: -0.1680, outB: 1.2428 +filter_2: inA: -0.1680, inB: 0.0000, outA: -0.1680, outB: 0.0168 +consumer: Read sample[03]: -0.1680 +filter_0: inA: 1.0000, inB: 1.2428, outA = outB: 2.2428 +filter_1: inA: 2.2428, inB: 0.0168, outA: 2.2428, outB: -1.3289 +filter_2: inA: 2.2428, inB: 0.0000, outA: 2.2428, outB: -0.2243 +consumer: Read sample[04]: +2.2428 diff --git a/dol/test/reference/example7_Windows XP.txt b/dol/test/reference/example7_Windows XP.txt new file mode 100644 index 0000000..9305352 --- /dev/null +++ b/dol/test/reference/example7_Windows XP.txt @@ -0,0 +1,31 @@ +init producer. +init consumer. +init filter_0: filter coefficient = -1.0 +init filter_1: filter coefficient = +0.1 +init filter_2: filter coefficient = -0.1 +producer: samples = { -9.0, +4.0, -2.0, +0.0, -3.0 } +producer: Write sample[00]: -9.0000 +producer: Write sample[01]: +4.0000 +filter_0: inA: -9.0000, inB: 0.0000, outA = outB: -9.0000 +filter_1: inA: -9.0000, inB: 0.0000, outA: -9.0000, outB: -0.9000 +filter_2: inA: -9.0000, inB: 0.0000, outA: -9.0000, outB: 0.9000 +producer: Write sample[02]: -2.0000 +consumer: Read sample[00]: -9.0000 +filter_0: inA: 4.0000, inB: -0.9000, outA = outB: 3.1000 +filter_1: inA: 3.1000, inB: 0.9000, outA: 3.1000, outB: 1.2100 +filter_2: inA: 3.1000, inB: 0.0000, outA: 3.1000, outB: -0.3100 +producer: Write sample[03]: +0.0000 +consumer: Read sample[01]: +3.1000 +filter_0: inA: -2.0000, inB: 1.2100, outA = outB: -0.7900 +filter_1: inA: -0.7900, inB: -0.3100, outA: -0.7900, outB: -0.3890 +filter_2: inA: -0.7900, inB: 0.0000, outA: -0.7900, outB: 0.0790 +producer: Write sample[04]: -3.0000 +consumer: Read sample[02]: -0.7900 +filter_0: inA: 0.0000, inB: -0.3890, outA = outB: -0.3890 +filter_1: inA: -0.3890, inB: 0.0790, outA: -0.3890, outB: 0.0401 +filter_2: inA: -0.3890, inB: 0.0000, outA: -0.3890, outB: 0.0389 +consumer: Read sample[03]: -0.3890 +filter_0: inA: -3.0000, inB: 0.0401, outA = outB: -2.9599 +filter_1: inA: -2.9599, inB: 0.0389, outA: -2.9599, outB: -0.2571 +filter_2: inA: -2.9599, inB: 0.0000, outA: -2.9599, outB: 0.2960 +consumer: Read sample[04]: -2.9599 diff --git a/dol/test/reference/examplesingleprocess_Linux.txt b/dol/test/reference/examplesingleprocess_Linux.txt new file mode 100644 index 0000000..1cbbe87 --- /dev/null +++ b/dol/test/reference/examplesingleprocess_Linux.txt @@ -0,0 +1,30 @@ +task_0: 0 +task_1: 0 +task_2: 0 +task_0: 1 +task_1: 1 +task_2: 1 +task_0: 2 +task_1: 2 +task_2: 2 +task_0: 3 +task_1: 3 +task_2: 3 +task_0: 4 +task_1: 4 +task_2: 4 +task_0: 5 +task_1: 5 +task_2: 5 +task_0: 6 +task_1: 6 +task_2: 6 +task_0: 7 +task_1: 7 +task_2: 7 +task_0: 8 +task_1: 8 +task_2: 8 +task_0: 9 +task_1: 9 +task_2: 9 diff --git a/dol/test/reference/examplesingleprocess_Windows XP.txt b/dol/test/reference/examplesingleprocess_Windows XP.txt new file mode 100644 index 0000000..1cbbe87 --- /dev/null +++ b/dol/test/reference/examplesingleprocess_Windows XP.txt @@ -0,0 +1,30 @@ +task_0: 0 +task_1: 0 +task_2: 0 +task_0: 1 +task_1: 1 +task_2: 1 +task_0: 2 +task_1: 2 +task_2: 2 +task_0: 3 +task_1: 3 +task_2: 3 +task_0: 4 +task_1: 4 +task_2: 4 +task_0: 5 +task_1: 5 +task_2: 5 +task_0: 6 +task_1: 6 +task_2: 6 +task_0: 7 +task_1: 7 +task_2: 7 +task_0: 8 +task_1: 8 +task_2: 8 +task_0: 9 +task_1: 9 +task_2: 9 diff --git a/dol/test/runtests.xml b/dol/test/runtests.xml new file mode 100644 index 0000000..f6578e9 --- /dev/null +++ b/dol/test/runtests.xml @@ -0,0 +1,96 @@ + + + + + + Ant build file to run tests. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dol/test/src/test/test/TreeValidator.java b/dol/test/src/test/test/TreeValidator.java new file mode 100644 index 0000000..4deede8 --- /dev/null +++ b/dol/test/src/test/test/TreeValidator.java @@ -0,0 +1,78 @@ +package test.test; + +import java.io.File; + +import test.util.XMLValidator; + +/** + * Class to check well-formedness and validity of XML documents. + */ +public class TreeValidator { + + /** + * Check the well-formedness and validity of XML files. + * + * @param args Specifies the XML file to be checked or the + * directory (including subdirectories) where XML + * and XSD files are checked. Multiple files or + * directories can be specified. + */ + public static void main(String args[]) throws Exception { + System.out.println("Run TreeValidator."); + if (args.length == 0) + browseDirectoryTree("./"); + else { + for (int k = 0; k < args.length; k++) + browseDirectoryTree(args[k]); + } + System.out.println("Finished."); + } + + /** + * Method which defines what is done for each file found in the + * directory tree: When the file is an XML or XSD file, it is + * validated using + * {@link test.util.XMLValidator#isValid(java.lang.String)}. + * + * @param filename file to process + */ + protected static void processFile(String filename) throws Exception { + if (filename.endsWith("xml") || filename.endsWith("xsd")) { + System.out.println("Checking " + filename + "..."); + if (!XMLValidator.isValid(filename)) + throw new Exception("File not valid."); + } + } + + /** + * Iterate through the given directory tree. For each file found in + * the file, the method {@link #processFile(java.lang.String)} is + * called. + * + * @param path the path of the directory to be browsed + */ + protected static void browseDirectoryTree(String path) throws Exception { + File file = new File(path); + + if (!file.exists()) return; + if (!file.isDirectory()) return; + + String filepath = file.getPath(); + String filename = file.getName(); + + //loop through files in directory + String[] files = file.list(); + + for (int k = 0; k < files.length; k++) { + File newfile = new File(file.getPath(), files[k]); + if (newfile.isFile()) + processFile(path + System.getProperty("file.separator") + + files[k]); + else if (newfile.isDirectory()) { + browseDirectoryTree(file.getPath() + + System.getProperty("file.separator") + + files[k]); + } + } + } +} diff --git a/dol/test/src/test/util/Diff.java b/dol/test/src/test/util/Diff.java new file mode 100644 index 0000000..dd0f73e --- /dev/null +++ b/dol/test/src/test/util/Diff.java @@ -0,0 +1,46 @@ +package test.util; + +import java.io.BufferedReader; +import java.io.FileReader; + +/** + * Class to diff two files + */ +public class Diff { + + /** + * Main. + * + * @param args names of the two files to compare + */ + public static void main(String[] args) throws Exception { + if (args.length != 2) { + //System.out.println("Usage: java Diff file1 file2"); + throw new Exception("Usage: java Diff file1 file2"); + } + + try { + BufferedReader fileOneReader = new BufferedReader(new FileReader(args[0])); + BufferedReader fileTwoReader = new BufferedReader(new FileReader(args[1])); + + String lineOne, lineTwo; + while ((lineOne = fileOneReader.readLine()) != null) { + lineTwo = fileTwoReader.readLine(); + if (lineTwo == null) + throw new Exception("Files are not equal: " + + args[1] + " is shorter."); + else if (!lineOne.equals(lineTwo)) + throw new Exception("Files do not match."); + } + + if ((lineTwo = fileTwoReader.readLine()) != null) + throw new Exception("Files are not equal: " + + args[0] + " is shorter."); + } + catch (Exception e) { + throw e; + } + + //System.out.println("Files are equal."); + } +} diff --git a/dol/test/src/test/util/XMLValidator.java b/dol/test/src/test/util/XMLValidator.java new file mode 100644 index 0000000..eba16cd --- /dev/null +++ b/dol/test/src/test/util/XMLValidator.java @@ -0,0 +1,56 @@ +package test.util; + +import java.util.ResourceBundle; +import java.io.IOException; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + * Class to check well-formedness and validity of XML documents. + */ +public class XMLValidator { + + /** + * Check the well-formedness and validity of an XML document. + * The file is checked against the schema which is referenced from + * within the file. When the document is an XML schema, it is checked + * against http://www.w3.org/2001/XMLSchema.xsd. + * + * @param filename filename of the file to be checked + */ + public static boolean isValid(String filename) { + ResourceBundle resourceBundle = ResourceBundle.getBundle("test"); + + SAXBuilder builder = new SAXBuilder(true); + builder.setFeature("http://xml.org/sax/features/validation", + true); + builder.setFeature("http://apache.org/xml/features/validation/" + + "schema", true); + builder.setFeature("http://apache.org/xml/features/validation/" + + "schema-full-checking", true); + builder.setProperty("http://apache.org/xml/properties/schema/" + + "external-schemaLocation", + "http://www.w3.org/2001/XMLSchema " + + "http://www.w3.org/2001/XMLSchema.xsd " + + resourceBundle.getString("LOCAL_SCHEMAS")); + + try { + builder.build(filename); + } + catch (JDOMException e) { + System.out.println("Found an error in " + filename + "."); + System.out.println(e.getMessage()); + System.out.println(""); + return false; + } + catch (IOException e) { + System.out.println("Found an error in " + filename + "."); + System.out.println(e.getMessage()); + System.out.println(""); + return false; + } + + return true; + } +} diff --git a/dol/test/test.properties b/dol/test/test.properties new file mode 100644 index 0000000..0a6c222 --- /dev/null +++ b/dol/test/test.properties @@ -0,0 +1,9 @@ +#local schemas for validation of XML files +LOCAL_SCHEMAS = \ + http://www.tik.ee.ethz.ch/~shapes/schema/PROCESSNETWORK \ + file:///@schema_path@/processnetwork.xsd \ + http://www.tik.ee.ethz.ch/~shapes/schema/ARCHITECTURE \ + file:///@schema_path@/architecture.xsd \ + http://www.tik.ee.ethz.ch/~shapes/schema/MAPPING \ + file:///@schema_path@/mapping.xsd + -- 2.30.2

L6@o%s<_%o%ek78j={_oW!2?4cxol$q*7DWFmS`n)c=;Ktq zJ549$5p3zC**v?1HvRUVNQ`_`4}w#G4?YHuY#$XU6!)snH_Lg-bP7IF_HL40#b301 zhm6t4lrKFm8y87|htKUiHBs#4HxxODS*}En5Z5?;ExbV}5>UcKD38};>V+^z&S0a! z%eVP6Vo8^dMep<3<;?u#5X9zn6BNw-ND^zJoU}IXsJ=}+Rp&Q){utzdsDtLAK$1d6 zk{YZ{f>FK?MaH8rsdz%92p6p|E_}WxG_|Z}i2t)`Q06Gy4k1I5yxDJSC|I$-mtFoU zT~)YKU}Jv+0kUt}!tr0;dJBCwJ6a)Q6MbhZCl!4wb0d8xb6Xo>2M1dRF?|~&tN$2( z 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�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