/*******************************************************************************
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package hr.fer.zemris.vhdllab.service.extractor.testbench;
import hr.fer.zemris.vhdllab.applets.editor.newtb.enums.TimeScale;
import hr.fer.zemris.vhdllab.applets.editor.newtb.exceptions.UniformTestbenchParserException;
import hr.fer.zemris.vhdllab.applets.editor.newtb.model.Testbench;
import hr.fer.zemris.vhdllab.applets.editor.newtb.model.TestbenchParser;
import hr.fer.zemris.vhdllab.applets.editor.newtb.model.signals.Signal;
import hr.fer.zemris.vhdllab.applets.editor.newtb.model.signals.SignalChange;
import hr.fer.zemris.vhdllab.entity.File;
import hr.fer.zemris.vhdllab.service.ci.CircuitInterface;
import hr.fer.zemris.vhdllab.service.ci.Port;
import hr.fer.zemris.vhdllab.service.exception.CircuitInterfaceExtractionException;
import hr.fer.zemris.vhdllab.service.exception.DependencyExtractionException;
import hr.fer.zemris.vhdllab.service.exception.VhdlGenerationException;
import hr.fer.zemris.vhdllab.service.extractor.AbstractMetadataExtractor;
import hr.fer.zemris.vhdllab.service.result.Result;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.NotImplementedException;
public class TestbenchMetadataExtractor extends AbstractMetadataExtractor {
@Override
public CircuitInterface extractCircuitInterface(File file)
throws CircuitInterfaceExtractionException {
return new CircuitInterface(file.getName());
}
@Override
protected CircuitInterface doExtractCircuitInterface(String data)
throws CircuitInterfaceExtractionException {
throw new NotImplementedException();
}
@Override
protected Set<String> doExtractDependencies(String data)
throws DependencyExtractionException {
Testbench tb;
try {
tb = TestbenchParser.parseXml(data);
} catch (UniformTestbenchParserException e) {
throw new DependencyExtractionException(e);
}
return Collections.singleton(tb.getSourceName());
}
@Override
public Result generateVhdl(File file) throws VhdlGenerationException {
Testbench tbInfo;
try {
tbInfo = TestbenchParser.parseXml(file.getData());
} catch (UniformTestbenchParserException e) {
throw new VhdlGenerationException(e);
}
String name = tbInfo.getSourceName();
File source = fileDao.findByName(file.getProject().getId(), name);
CircuitInterface ci = metadataExtractor.extractCircuitInterface(source);
String vhdl = null;
try {
vhdl = generirajVHDL(file.getName(), name, ci, tbInfo);
} catch (Exception e) {
throw new VhdlGenerationException(e);
}
return new Result(vhdl);
}
@Override
protected Result doGenerateVhdl(String data) throws VhdlGenerationException {
throw new NotImplementedException();
}
/**
* Create a testbench VHDL code from circuit interface and testbench.
*
* @param testBenchFileName
* name of the created testbench for particular component
* @param testedFileName
* name of the component for which we created testbench
* @param ci
* a circuit interface
* @param tbInfo
* information about testbench from inducement
* @return a string representing testbench VHDL.
*
* @throws NullPointerException
* if <code>ci</code> or <code>testedFileName</code> or
* <code>testBnechFileName</code> or <code>tbInfo</code> is
* <code>null</code>.
*
*/
private String generirajVHDL(String testBenchFileName,
String testedFileName, CircuitInterface ci, Testbench tbInfo) {
if (testedFileName == null)
throw new NullPointerException("Tested filename can not be null.");
if (testBenchFileName == null)
throw new NullPointerException(
"TestBench filename can not be null.");
if (tbInfo == null)
throw new NullPointerException(
"TestBench filename can not be null.");
long TestBenchLength = tbInfo.getTestBenchLength();
int bufferSize = 0;
if (tbInfo.getSimulationLength() != 0)
TestBenchLength = tbInfo.getSimulationLength();
for (Signal s : tbInfo.getSignals()) {
bufferSize += s.getSignalChangeList(0, TestBenchLength).size() * 12;
}
StringBuilder vhdl = new StringBuilder(bufferSize);
vhdl.append("library IEEE;\n").append("use IEEE.STD_LOGIC_1164.ALL;\n")
.append("\n").append("entity ").append(testBenchFileName)
.append(" is \n").append("end ").append(testBenchFileName)
.append(";\n").append("\n").append(
"architecture structural of ")
.append(testBenchFileName).append(" is \n");
populateComponent(ci, vhdl);
vhdl.append("end component;\n").append("\n");
populateSignals(ci, vhdl);
vhdl.append("begin\n");
populatePortMap(ci, vhdl);
vhdl.append("\n").append("\tprocess").append("\n").append("\tbegin")
.append("\n");
populateProcess(ci, tbInfo, vhdl);
vhdl.append("\n").append("\tend process;\n").append("end structural;");
return vhdl.toString();
}
/**
* Populate a process in testbench VHDL.
*
* @param ci
* a circuit interface from where to draw information.
* @param tbInfo
* information about testbench from inducement.
* @param vhdl
* a string builder where to store testbench VHDL.
*/
private void populateProcess(CircuitInterface ci, Testbench tbInfo,
StringBuilder vhdl) {
List<ChangesInMoment> list = createProcessList(ci, tbInfo);
tbInfo.getTimeScale();
long time = -1;
for (ChangesInMoment slot : list) {
if (time != -1) {
vhdl.append("\t\twait for ").append(
slot.getTime()
/ TimeScale
.getMultiplier(tbInfo.getTimeScale())
- time
/ TimeScale
.getMultiplier(tbInfo.getTimeScale()))
.append(" ").append(tbInfo.getTimeScale().toString())
.append(";\n\n");
}
for (ChangeWithSignalName change : slot.getList()) {
String state = change.getChange().getSignalValue();
vhdl.append("\t\t").append(
getTestbenchSignal(change.getSignalName())).append(
" <= ");
if (ci.getPort(change.getSignalName()).isScalar())
vhdl.append("\'").append(state).append("\';\n");
else
vhdl.append("\"").append(state).append("\";\n");
}
time = slot.getTime();
}
vhdl.append("\t\twait for ").append(
tbInfo.getPeriodLength()
/ TimeScale.getMultiplier(tbInfo.getTimeScale()))
.append(" ").append(tbInfo.getTimeScale().toString()).append(
";\n");
vhdl.append("\t\twait;\n");
}
/**
* Create a process list. This method will create a list of objects
* ChangesInMoment from where method populateProcess(CircuitInterface,
* Testbench, StringBuilder) will draw information regarding signal changes
* written in Testbench.
*
* @param ci
* a circuit interface from where to draw information.
* @param tbInfo
* information about testbench from inducement.
*/
private List<ChangesInMoment> createProcessList(CircuitInterface ci,
Testbench tbInfo) {
ChangesInMoment tmp = new ChangesInMoment(0);
Map<ChangesInMoment, ChangesInMoment> table = new HashMap<ChangesInMoment, ChangesInMoment>();
for (Signal s : tbInfo.getSignals()) {
Port port = ci.getPort(s.getName());
if (port == null || !port.isIN())
continue;
long TestBenchLength = tbInfo.getTestBenchLength();
if (tbInfo.getSimulationLength() != 0)
TestBenchLength = tbInfo.getSimulationLength();
for (SignalChange i : s.getSignalChangeList(0, TestBenchLength)) {
tmp.setTime(i.getTime());
ChangesInMoment list = table.get(tmp);
if (list == null) {
list = new ChangesInMoment(tmp.getTime());
table.put(list, list);
}
ChangeWithSignalName imp = new ChangeWithSignalName(
s.getName(), i);
list.addChangeWithSignalName(imp);
}
}
Collection<ChangesInMoment> c = table.values();
List<ChangesInMoment> l = new ArrayList<ChangesInMoment>(c);
Collections.sort(l);
return l;
}
/**
* This class contains information about each signal change in the
* particular time moment. It contains time of change and list of
* ChangeWithSignalName structure.
*
* @author Ivan Kmetovic
*
*/
private static class ChangesInMoment implements Comparable<ChangesInMoment> {
private long time;
private List<ChangeWithSignalName> list = new ArrayList<ChangeWithSignalName>();
private List<ChangeWithSignalName> list_ro = Collections
.unmodifiableList(list);
/**
* Construct an instance of this class using a time. A time must be a
* non-negative number.
*
* @param time
*/
public ChangesInMoment(long time) {
setTime(time);
}
/**
* Getter method for time of signal change.
*
* @return a time of signal change.
*/
public long getTime() {
return time;
}
/**
* Setter method for a time of signal change.
*
* @param time
* a time of signal change.
* @throws IllegalArgumentException
* if <code>time</code> is null.
*/
public void setTime(long time) {
if (time < 0)
throw new IllegalArgumentException("Time can not be negative.");
this.time = time;
}
/**
* Getter method for a read-only list of ChangeWithSignalName.
*
* @return a read-only list of ChangeWithSignalName.
*/
public List<ChangeWithSignalName> getList() {
return list_ro;
}
/**
* Append an ChangeWithSignalName to the end of this list.
*
* @param i
* an ChangeWithSignalName to add.
* @return <code>true</code> if <code>i</code> has been added;
* <code>false</code> otherwise.
* @throws NullPointerException
* if <code>i</code> is <code>null</code>.
*/
public boolean addChangeWithSignalName(ChangeWithSignalName i) {
if (i == null)
throw new NullPointerException(
"ChangeWithSignalName can not be null.");
if (getList().contains(i))
return false;
list.add(i);
return true;
}
/**
* Compares this object ChangesInMoment with the specified object for
* order. These two objects are compared by time.
*
* @param other
* <code>ChangesInMoment</code> to be compared.
* @return the value 0 if the argument object ChangesInMoment is equal
* to this object; a value less than 0 if this object has time
* less than the object argument; and a value greater than 0 if
* this object has time greater than the object argument.
* @throws ClassCastException
* if the specified object's type prevents it from being
* compared to this ChangesInMoment.
*/
public int compareTo(ChangesInMoment other) {
long res = this.time - other.getTime();
if (res < 0)
return -1;
else if (res > 0)
return 1;
else
return 0;
}
/**
* Compares this object ChangesInMoment to the specified object. Returns
* <code>true</code> if and only if the specified object is also a
* <code>ChangesInMoment</code> and if times are the same.
*
* @param o
* the object to compare this <code>ChangesInMoment</code>
* against.
* @return <code>true</code> if <code>ChangesInMoment</code> are equal;
* <code>false</code> otherwise.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof ChangesInMoment))
return false;
ChangesInMoment other = (ChangesInMoment) o;
return this.time == other.getTime();
}
/**
* Returns a hash code value for this <code>ChangesInMoment</code>
* instance. The hash code of <code>ChangesInMoment</code> instance is
* hash code of time.
* <p>
* This ensures that <code>s1.equals(s2)</code> implies that
* <code>s1.hashCode() == s2.hashCode()</code> for any two
* ChangesInMoment objects, <code>s1</code> and <code>s2</code>, as
* required by the general contract of <code>Object.hashCode</code>.
*
* @return a hash code value for this <code>ChangesInMoment</code>
* instance.
* @see java.lang.String#hashCode()
* @see java.util.List#hashCode()
*/
@Override
public int hashCode() {
return Long.valueOf(time).hashCode();
}
}
/**
* This class contains information regarding a signal change in a certain
* time. It contains of signal name and an class that describes signal
* change.
*
* @author Ivan Kmetovic
*
*/
private static class ChangeWithSignalName {
private String signalName;
private SignalChange change;
/**
* Construct an instance of this class using a signal name and an signal
* change to explain an ChangeWithSignalName. A signal name must have
* the following format:
* <ul>
* <li>it must contain only alpha (only letters of english alphabet),
* numeric (digits 0 to 9) or underscore (_) characters
* <li>it must not start with a non-alpha character
* <li>it must not end with an underscore character
* <li>it must not contain an underscore character after an underscore
* character
* <li>it must not be a reserved word (check at
* hr.fer.zemris.vhdllab.utilities.NotValidVHDLNames.txt)
* </ul>
*
* @param name
* a name of a signal.
* @param change
* information about signal change
* @throws NullPointerException
* if <code>name</code> or an <code>impulse</code> is
* <code>null</code>.
* @throws IllegalArgumentException
* if <code>name</code> is not of correct format.
*/
public ChangeWithSignalName(String name, SignalChange change) {
if (change == null)
throw new NullPointerException("Change can not be null.");
if (name == null)
throw new NullPointerException("Signal name can not be null.");
this.signalName = name;
this.change = change;
}
/**
* Getter method for an SignalChange.
*
* @return a description of signal change.
*/
public SignalChange getChange() {
return change;
}
/**
* Getter method for a name of a signal.
*
* @return a name of a signal.
*/
public String getSignalName() {
return signalName;
}
/**
* Compares this ChangeWithSignalName to the specified object. Returns
* <code>true</code> if and only if the specified object is also a
* <code>ChangeWithSignalName</code> and if signal names and described
* signal changes are the same.
*
* @param o
* the object to compare this
* <code>ChangeWithSignalName</code> against.
* @return <code>true</code> if <code>ChangeWithSignalName</code> are
* equal; <code>false</code> otherwise.
*/
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (!(o instanceof ChangeWithSignalName))
return false;
ChangeWithSignalName other = (ChangeWithSignalName) o;
return other.getSignalName().equalsIgnoreCase(this.signalName)
&& other.getChange().equals(this.change);
}
}
/**
* Populate a port map in testbench VHDL.
*
* @param ci
* a circuit interface from where to draw information.
* @param vhdl
* a string builder where to store testbench VHDL.
*/
private void populatePortMap(CircuitInterface ci, StringBuilder vhdl) {
vhdl.append("uut: ").append(ci.getName()).append(" port map ( ");
for (Port p : ci.getPorts()) {
vhdl.append(getTestbenchSignal(p.getName())).append(", ");
}
vhdl.deleteCharAt(vhdl.length() - 2); // deleting last ','
vhdl.append(");\n");
}
/**
* Populate a signal list in testbench VHDL.
*
* @param ci
* a circuit interface from where to draw information.
* @param vhdl
* a string builder where to store testbench VHDL.
*/
private void populateSignals(CircuitInterface ci, StringBuilder vhdl) {
for (Port p : ci.getPorts()) {
vhdl.append("\tsignal ").append(getTestbenchSignal(p.getName()))
.append(" : ").append(p.getTypeName());
if (p.isVector())
vhdl.append("(").append(p.getFrom()).append(" ").append(
p.getDirectionName()).append(" ").append(p.getTo())
.append(")");
vhdl.append(";\n");
}
}
/**
* Create a testbench signal name from a given signal name. Testbench signal
* name will have the following format: <blockquote> tb_'signalName'
* </blockquote>
*
* @param name
* a name of a signal.
* @return a testbench signal name.
*/
private String getTestbenchSignal(String name) {
return "tb_" + name;
}
/**
* Populate a component in testbench VHDL.
*
* @param ci
* a circuit interface from where to draw information.
* @param vhdl
* a string builder where to store testbench VHDL.
*/
private void populateComponent(CircuitInterface ci, StringBuilder vhdl) {
vhdl.append("component ").append(ci.getName()).append(" is port (\n");
for (Port p : ci.getPorts()) {
vhdl.append("\t").append(p.getName()).append(" : ").append(
p.getDirection().toString()).append(" ").append(
p.getTypeName());
if (p.isVector())
vhdl.append("(").append(p.getFrom()).append(" ").append(
p.getDirectionName()).append(" ").append(p.getTo())
.append(")");
vhdl.append(";\n");
}
vhdl.deleteCharAt(vhdl.length() - 2) // deleting last ';'
.append(");\n");
}
}