Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
mbe-tools
TTool
Commits
281c5086
Commit
281c5086
authored
Mar 13, 2019
by
Ludovic Apvrille
Browse files
Merge branch 'ScheduleDisplay' into 'master'
display of tasks scheduling See merge request
!118
parents
f270ba6b
5a80ed70
Changes
1
Hide whitespace changes
Inline
Side-by-side
src/main/java/tmltranslator/dsez3engine/OptimizationModel.java
View file @
281c5086
...
...
@@ -6,16 +6,13 @@ import tmltranslator.*;
import
javax.swing.*
;
import
java.lang.reflect.Array
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.HashMap
;
import
java.util.*
;
public
class
OptimizationModel
{
private
Map
<
String
,
Integer
>
optimizedSolutionX
=
new
HashMap
<
String
,
Integer
>();
private
Map
<
String
,
Integer
>
optimizedSolutionStart
=
new
HashMap
<
String
,
Integer
>();
private
Map
<
String
,
Integer
>
optimizedSolutionStart
=
new
HashMap
<>();
private
InputInstance
inputInstance
;
private
TMLMapping
tmlMapping
;
...
...
@@ -47,7 +44,7 @@ public class OptimizationModel {
this
.
optimizedSolutionStart
=
optimizedSolutionStart
;
}
public
OptimizationResult
findOptimizedMapping
()
{
public
OptimizationResult
findOptimizedMapping
()
{
Context
ctx
;
OptimizationResult
result
=
null
;
try
{
...
...
@@ -151,7 +148,7 @@ public class OptimizationModel {
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
...
...
@@ -170,9 +167,9 @@ public class OptimizationModel {
//Constraint on start >=0
BoolExpr
[]
c_bound_start
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
start
[
t
]
=
ctx
.
mkIntConst
(
"start_"
+
taskCast
.
getID
());
c_bound_start
[
t
]
=
ctx
.
mkGe
(
start
[
t
],
ctx
.
mkInt
(
0
));
...
...
@@ -188,7 +185,7 @@ public class OptimizationModel {
//TraceManager.addDev("\nUnique task-CPU mapping constraints (3)");
BoolExpr
[]
c_unique_x
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
ArithExpr
sum_X
=
ctx
.
mkAdd
(
X
[
t
]);
...
...
@@ -201,9 +198,9 @@ public class OptimizationModel {
//Feasible Task map: ∀t, SUM p in F(t) (X tp) = 1
//TraceManager.addDev("\nFeasible task-CPU mapping constraints (4)");
BoolExpr
[]
c_feasibleMapX
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
ArithExpr
sum_X
=
ctx
.
mkInt
(
0
);
for
(
HwNode
hwNode
:
inputInstance
.
getFeasibleCPUs
(
taskCast
))
{
...
...
@@ -229,9 +226,9 @@ public class OptimizationModel {
IntExpr
mem
=
ctx
.
mkInt
(
inputInstance
.
getLocalMemoryOfHwExecutionNode
(
hwNode
).
memorySize
);
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
IntExpr
bin_plus_bout
=
ctx
.
mkInt
(
inputInstance
.
getBufferIn
(
taskCast
)
+
inputInstance
.
getBufferOut
(
taskCast
));
...
...
@@ -294,7 +291,7 @@ public class OptimizationModel {
TMLTask
producer
=
channelCast
.
getOriginTask
();
int
prodIndex
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
producer
);
TraceManager
.
addDev
(
"prodIndex"
+
prodIndex
+
"producer "
+
producer
+
inputInstance
.
getModeling
().
getTasks
());
TraceManager
.
addDev
(
"prodIndex"
+
prodIndex
+
"producer "
+
producer
+
inputInstance
.
getModeling
().
getTasks
());
TMLTask
consumer
=
channelCast
.
getDestinationTask
();
...
...
@@ -329,10 +326,10 @@ public class OptimizationModel {
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
int
p
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
hwNode
);
for
(
Object
taski
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
taski
:
inputInstance
.
getModeling
().
getTasks
())
{
int
ti
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
taski
);
TMLTask
taskiCast
=
(
TMLTask
)
taski
;
TMLTask
taskiCast
=
(
TMLTask
)
taski
;
//Calculate End times of ti
ArithExpr
wceti
=
ctx
.
mkInt
(
0
);
...
...
@@ -348,7 +345,7 @@ public class OptimizationModel {
for
(
Object
taskj
:
inputInstance
.
getModeling
().
getTasks
())
{
int
tj
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
taskj
);
TMLTask
taskjCast
=
(
TMLTask
)
taskj
;
TMLTask
taskjCast
=
(
TMLTask
)
taskj
;
if
(
tj
!=
ti
)
{
...
...
@@ -388,10 +385,10 @@ public class OptimizationModel {
//Grouping remaining constraints of the model
// TraceManager.addDev("\nAll mapping constraints");
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
TMLTask
taskCast
=
(
TMLTask
)
task
;
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
int
p
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
hwNode
);
...
...
@@ -422,7 +419,7 @@ public class OptimizationModel {
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
TMLTask
taskCast
=
(
TMLTask
)
task
;
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
int
p
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
hwNode
);
...
...
@@ -438,7 +435,7 @@ public class OptimizationModel {
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
TMLTask
taskCast
=
(
TMLTask
)
task
;
optimized_result_start
[
t
]
=
m
.
evaluate
(
start
[
t
],
false
);
TraceManager
.
addDev
(
"start["
+
taskCast
.
getName
()
+
"] = "
+
optimized_result_start
[
t
]);
...
...
@@ -457,7 +454,7 @@ public class OptimizationModel {
}
//findFeasibleMapping()
public
OptimizationResult
findOptimizedMapping
(
Context
ctx
)
{
public
OptimizationResult
findOptimizedMapping
(
Context
ctx
)
{
OptimizationResult
result
=
new
OptimizationResult
();
...
...
@@ -481,10 +478,10 @@ public class OptimizationModel {
//TraceManager.addDev("\nDefining the bounds of Xtp (1)");
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
...
...
@@ -503,10 +500,10 @@ public class OptimizationModel {
//Constraint on start >=0
BoolExpr
[]
c_bound_start
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
start
[
t
]
=
ctx
.
mkIntConst
(
"start_"
+
taskCast
.
getName
());
c_bound_start
[
t
]
=
ctx
.
mkGe
(
start
[
t
],
ctx
.
mkInt
(
0
));
mapping_constraints
=
ctx
.
mkAnd
(
mapping_constraints
,
c_bound_start
[
t
]);
...
...
@@ -521,7 +518,7 @@ public class OptimizationModel {
//TraceManager.addDev("\nUnique task-CPU mapping constraints (3)");
BoolExpr
[]
c_unique_x
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
// TODO or use the local instance modeling
ArithExpr
sum_X
=
ctx
.
mkAdd
(
X
[
t
]);
...
...
@@ -534,11 +531,11 @@ public class OptimizationModel {
//Feasible Task map: ∀t, SUM p in F(t) (X tp) = 1
//TraceManager.addDev("\nFeasible task-CPU mapping constraints (4)");
BoolExpr
[]
c_feasibleMapX
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
ArithExpr
sum_X
=
ctx
.
mkInt
(
0
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
for
(
HwNode
hwNode
:
inputInstance
.
getFeasibleCPUs
(
taskCast
))
{
...
...
@@ -567,7 +564,7 @@ public class OptimizationModel {
for
(
Object
tmlTask
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
tmlTask
);
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
TMLTask
taskCast
=
(
TMLTask
)
tmlTask
;
IntExpr
bin_plus_bout
=
ctx
.
mkInt
(
inputInstance
.
getBufferIn
(
taskCast
)
+
inputInstance
.
getBufferOut
(
taskCast
));
ArithExpr
bin_plus_bout_times_X
=
ctx
.
mkMul
(
bin_plus_bout
,
X
[
t
][
p
]);
...
...
@@ -620,11 +617,11 @@ public class OptimizationModel {
// TraceManager.addDev("\nPrecedence constraints (11)");
BoolExpr
[]
c_precedence
=
new
BoolExpr
[
inputInstance
.
getModeling
().
getChannels
().
size
()];
for
(
Object
channel
:
inputInstance
.
getModeling
().
getChannels
())
{
for
(
Object
channel
:
inputInstance
.
getModeling
().
getChannels
())
{
int
c
=
inputInstance
.
getModeling
().
getChannels
().
indexOf
(
channel
);
//for each channel get producer and consumer
TMLChannel
channelCast
=
(
TMLChannel
)
channel
;
TMLChannel
channelCast
=
(
TMLChannel
)
channel
;
TMLTask
producer
=
channelCast
.
getOriginTask
();
int
prodIndex
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
producer
);
...
...
@@ -663,13 +660,13 @@ public class OptimizationModel {
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
int
p
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
hwNode
);
for
(
Object
taski
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
taski
:
inputInstance
.
getModeling
().
getTasks
())
{
int
ti
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
taski
);
//Calculate End times of ti
ArithExpr
wceti
=
ctx
.
mkInt
(
0
);
TMLTask
taskiCast
=
(
TMLTask
)
taski
;
TMLTask
taskiCast
=
(
TMLTask
)
taski
;
for
(
HwExecutionNode
ihwNode
:
inputInstance
.
getFeasibleCPUs
(
taskiCast
))
{
int
ip
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
ihwNode
);
...
...
@@ -680,7 +677,7 @@ public class OptimizationModel {
ArithExpr
endti
=
ctx
.
mkAdd
(
start
[
ti
],
wceti
);
for
(
Object
taskj
:
inputInstance
.
getModeling
().
getTasks
())
{
for
(
Object
taskj
:
inputInstance
.
getModeling
().
getTasks
())
{
int
tj
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
taskj
);
...
...
@@ -690,7 +687,7 @@ public class OptimizationModel {
ArithExpr
wcetj
=
ctx
.
mkInt
(
0
);
TMLTask
taskjCast
=
(
TMLTask
)
taskj
;
TMLTask
taskjCast
=
(
TMLTask
)
taskj
;
for
(
HwExecutionNode
jhwNode
:
inputInstance
.
getFeasibleCPUs
(
taskjCast
))
{
...
...
@@ -783,11 +780,14 @@ public class OptimizationModel {
Expr
[][]
optimized_result_X
=
new
Expr
[
inputInstance
.
getModeling
().
getTasks
().
size
()][
inputInstance
.
getArchitecture
().
getCPUs
().
size
()];
Expr
[]
optimized_result_start
=
new
Expr
[
inputInstance
.
getModeling
().
getTasks
().
size
()];
tmlMapping
=
new
TMLMapping
<>(
inputInstance
.
getModeling
(),
inputInstance
.
getArchitecture
(),
false
);
tmlMapping
=
new
TMLMapping
<>(
inputInstance
.
getModeling
(),
inputInstance
.
getArchitecture
(),
false
);
outputToDisplay
=
"The optimal mapping solution is:\n\n"
;
outputToDisplay
=
"The optimal mapping solution is:\n\n"
;
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
outputToDisplay
=
outputToDisplay
+
"(1) Spatial mapping of tasks:\n"
;
outputToDisplay
=
outputToDisplay
+
"\nTASK --> PLATFORM NODE\n"
;
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
...
...
@@ -795,61 +795,86 @@ public class OptimizationModel {
//evaluate optimal solution
optimized_result_X
[
t
][
p
]
=
m
.
evaluate
(
X
[
t
][
p
],
true
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
TMLTask
taskCast
=
(
TMLTask
)
task
;
if
(
Integer
.
parseInt
(
optimized_result_X
[
t
][
p
].
toString
())
==
1
){
if
(
Integer
.
parseInt
(
optimized_result_X
[
t
][
p
].
toString
())
==
1
)
{
//mapping of task on hwExecutionNode
tmlMapping
.
addTaskToHwExecutionNode
(
taskCast
,(
HwExecutionNode
)
hwNode
);
tmlMapping
.
addTaskToHwExecutionNode
(
taskCast
,
(
HwExecutionNode
)
hwNode
);
//mapping of R/W channels on local memory of hwExecutionNode TODO get(0)
if
(!
taskCast
.
getReadChannels
().
isEmpty
())
tmlMapping
.
addCommToHwCommNode
(
taskCast
.
getReadChannels
().
get
(
0
),
inputInstance
.
getLocalMemoryOfHwExecutionNode
(
hwNode
));
if
(!
taskCast
.
getReadChannels
().
isEmpty
())
tmlMapping
.
addCommToHwCommNode
(
taskCast
.
getReadChannels
().
get
(
0
),
inputInstance
.
getLocalMemoryOfHwExecutionNode
(
hwNode
));
if
(!
taskCast
.
getWriteChannels
().
isEmpty
())
tmlMapping
.
addCommToHwCommNode
(
taskCast
.
getWriteChannels
().
get
(
0
),
inputInstance
.
getLocalMemoryOfHwExecutionNode
(
hwNode
));
if
(!
taskCast
.
getWriteChannels
().
isEmpty
())
tmlMapping
.
addCommToHwCommNode
(
taskCast
.
getWriteChannels
().
get
(
0
),
inputInstance
.
getLocalMemoryOfHwExecutionNode
(
hwNode
));
outputToDisplay
=
outputToDisplay
+
"\n"
+
taskCast
.
getName
()
+
"
-->
"
+
hwNode
.
getName
();
outputToDisplay
=
outputToDisplay
+
"\n"
+
taskCast
.
getName
()
+
"
-->
"
+
hwNode
.
getName
();
}
optimizedSolutionX
.
put
(
"X["
+
taskCast
.
getName
()
+
"]["
+
hwNode
.
getName
()
+
"] = "
,
Integer
.
parseInt
(
optimized_result_X
[
t
][
p
]
.
toString
()));
.
toString
()));
// TraceManager.addDev("X[" + task.getName() + "][" + hwNode.getName() + "] = " + optimized_result_X[t][p]);
// TraceManager.addDev("X[" + task.getName() + "][" + hwNode.getName() + "] = " + optimized_result_X[t][p]);
}
TraceManager
.
addDev
(
"\n"
);
}
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
outputToDisplay
=
outputToDisplay
+
"\n\n(2) Temporal mapping of tasks:\n"
;
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
optimized_result_start
[
t
]
=
m
.
evaluate
(
start
[
t
],
false
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
TMLTask
taskCast
=
(
TMLTask
)
task
;
optimizedSolutionStart
.
put
(
"start["
+
taskCast
.
getName
()
+
"]
=
"
,
Integer
.
parseInt
(
optimized_result_start
[
t
].
toString
()));
//
optimizedSolutionStart.put("start[" + taskCast.getName() + "] ", Integer.parseInt(optimized_result_start[t].toString()));
// TraceManager.addDev("start[" + task.getName() + "] = " + optimized_result_start[t]);
// TraceManager.addDev("start[" + task.getName() + "] = " + optimized_result_start[t]);
}
/* for(Map.Entry<String, Integer> entry : optimizedSolutionX.entrySet()) {
outputToDisplay = outputToDisplay + entry.getKey() + " = " + entry.getValue() + "\n";
}*/
// reordering and displaying schedule
for
(
HwNode
hwNode
:
inputInstance
.
getArchitecture
().
getCPUs
())
{
int
p
=
inputInstance
.
getArchitecture
().
getCPUs
().
indexOf
(
hwNode
);
TreeMap
<
Integer
,
String
>
treeHwNode
=
new
TreeMap
<>();
outputToDisplay
=
outputToDisplay
+
"\n"
+
hwNode
.
getName
()
+
" : "
;
for
(
Object
task
:
inputInstance
.
getModeling
().
getTasks
())
{
int
t
=
inputInstance
.
getModeling
().
getTasks
().
indexOf
(
task
);
TMLTask
taskCast
=
(
TMLTask
)
task
;
if
(
Integer
.
parseInt
(
optimized_result_X
[
t
][
p
].
toString
())
==
1
)
{
treeHwNode
.
put
(
Integer
.
parseInt
(
optimized_result_start
[
t
].
toString
()),
taskCast
.
getName
());
}
}
for
(
Map
.
Entry
<
Integer
,
String
>
entry
:
treeHwNode
.
entrySet
())
{
outputToDisplay
=
outputToDisplay
+
entry
.
getValue
()
+
" at "
+
entry
.
getKey
()
+
" ; "
;
}
}
outputToDisplay
+=
"\n\n"
;
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
optimizedSolutionStart
.
entrySet
())
{
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
optimizedSolutionStart
.
entrySet
())
{
outputToDisplay
=
outputToDisplay
+
entry
.
getKey
()
+
" = "
+
entry
.
getValue
()
+
"\n"
;
}
TraceManager
.
addDev
(
outputToDisplay
);
result
.
mappingFound
=
true
;
result
.
resultingMapping
=
tmlMapping
;
}
else
{
outputToDisplay
=
"No suitable mapping could be found"
;
outputToDisplay
=
"No suitable mapping could be found"
;
TraceManager
.
addDev
(
outputToDisplay
);
result
.
mappingFound
=
false
;
}
...
...
@@ -860,6 +885,4 @@ public class OptimizationModel {
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment