diff --git a/src/main/java/de/neemann/digital/core/memory/DataField.java b/src/main/java/de/neemann/digital/core/memory/DataField.java index 544942e3e..ae8528073 100644 --- a/src/main/java/de/neemann/digital/core/memory/DataField.java +++ b/src/main/java/de/neemann/digital/core/memory/DataField.java @@ -10,6 +10,7 @@ import de.neemann.digital.hdl.hgs.HGSArray; import de.neemann.digital.lang.Lang; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -58,16 +59,6 @@ public class DataField implements HGSArray { this(Arrays.copyOf(dataField.data, newSize), newSize); } - /** - * Creates a new instance and fills it with the data in the given file - * - * @param file the file containing the data - * @throws IOException IOException - */ - public DataField(File file) throws IOException { - this(new InputStreamReader(new FileInputStream(file), "UTF-8")); - } - /** * Creates a new instance and fills it with the data in the given reader * @@ -116,7 +107,7 @@ public class DataField implements HGSArray { */ public void saveTo(File file) throws IOException { DataField df = getMinimized(); - try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8"))) { + try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { w.write("v2.0 raw"); w.newLine(); for (long l : df.getData()) { diff --git a/src/main/java/de/neemann/digital/core/memory/DataFieldImporter.java b/src/main/java/de/neemann/digital/core/memory/DataFieldImporter.java new file mode 100644 index 000000000..dedcf0b89 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/memory/DataFieldImporter.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.memory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * Helper to import data memory + */ +public final class DataFieldImporter { + private static final Logger LOGGER = LoggerFactory.getLogger(DataFieldImporter.class); + + private DataFieldImporter() { + } + + /** + * Imports a file and converts it into a DataField instance + * + * @param file the file to read + * @param dataBits the data bits of the target memory + * @return the DataField instance + * @throws IOException IOException + */ + public static DataField read(File file, int dataBits) throws IOException { + String name = file.getName().toLowerCase(); + if (name.endsWith(".hex")) { + try { + return new DataField(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + } catch (IOException e) { + LOGGER.info(file + ": could not read native hex, try intel hex"); + return readByteArray(file, dataBits, IntelHexReader::new); + } + } else { + LOGGER.info(file + ": read as binary"); + return readByteArray(file, dataBits, DataFieldImporter::readBinary); + } + } + + private static DataField readByteArray(File file, int dataBits, Reader reader) throws IOException { + DataField dataField = new DataField(0x10000); + reader.read(file, create(dataField, dataBits)); + return dataField.getMinimized(); + } + + interface DataArray { + void put(int addr, int aByte); + } + + static DataArray create(DataField dataField, int dataBits) { + if (dataBits <= 8) + return dataField::setData; + return new DataArrayMod(dataField, (dataBits - 1) / 8 + 1); + } + + private static final class DataArrayMod implements DataArray { + private final DataField dataField; + private final int div; + + private DataArrayMod(DataField dataField, int div) { + this.dataField = dataField; + this.div = div; + } + + @Override + public void put(int addr, int aByte) { + int a = addr / div; + int b = addr % div; + + long val = dataField.getDataWord(a); + val = val | ((((long) aByte) & 0xff) << (b * 8)); + dataField.setData(a, val); + } + } + + interface Reader { + void read(File file, DataArray dataArray) throws IOException; + } + + private static void readBinary(File file, DataArray dataArray) throws IOException { + try (InputStream in = new FileInputStream(file)) { + int d; + int addr = 0; + while ((d = in.read()) > 0) { + dataArray.put(addr, d); + addr++; + } + } + } + +} diff --git a/src/main/java/de/neemann/digital/core/memory/IntelHexReader.java b/src/main/java/de/neemann/digital/core/memory/IntelHexReader.java new file mode 100644 index 000000000..c8e96b022 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/memory/IntelHexReader.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.memory; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * Reader for intel hex files + */ +class IntelHexReader { + private final BufferedReader bufferedReader; + private final DataFieldImporter.DataArray dataArray; + private final int[] data; + private int segment = 0; + + /** + * Creates a new reader instance + * + * @param file the file to read + * @param dataArray the array to write the data to + * @throws IOException IOException + */ + IntelHexReader(File file, DataFieldImporter.DataArray dataArray) throws IOException { + this(new FileInputStream(file), dataArray); + } + + /** + * Creates a new reader instance + * + * @param inputStream the stream to read + * @param dataArray the array to write the data to + * @throws IOException IOException + */ + IntelHexReader(InputStream inputStream, DataFieldImporter.DataArray dataArray) throws IOException { + this(new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)), dataArray); + } + + private IntelHexReader(BufferedReader bufferedReader, DataFieldImporter.DataArray dataArray) throws IOException { + this.bufferedReader = bufferedReader; + this.dataArray = dataArray; + data = new int[300]; + read(); + } + + private void read() throws IOException { + String line; + while ((line = bufferedReader.readLine()) != null) { + int payload = parseLine(line); + switch (data[3]) { + case 0: + readData(payload); + break; + case 2: + readDataSegment(payload); + break; + } + } + } + + private void readDataSegment(int len) throws IOException { + if (len != 2) + throw new IOException("invalid segment address"); + segment = ((data[4] << 8) + data[5])<<4; + } + + private void readData(int len) { + int addr = (data[1] << 8) + data[2]; + for (int i = 0; i < len; i++) + dataArray.put(segment + addr + i, data[i + 4]); + } + + private int parseLine(String line) throws IOException { + if (line.charAt(0) != ':') + throw new IOException("not a intel hex file"); + + int addr = 0; + int p = 1; + while (p < line.length()) { + data[addr] = Integer.parseInt(line.substring(p, p + 2), 16); + addr++; + p += 2; + } + + int payload = addr - 5; + + if (payload < 0) + throw new IOException("not a intel hex file"); + + if (data[0] != payload) + throw new IOException("invalid record size"); + + int sum = 0; + for (int i = 0; i < addr; i++) + sum += data[i]; + + sum = sum & 0xff; + if (sum != 0) + throw new IOException("wrong checksum in intel hex file: 0x" + Integer.toHexString(sum)); + + return payload; + } +} diff --git a/src/main/java/de/neemann/digital/core/memory/ProgramMemory.java b/src/main/java/de/neemann/digital/core/memory/ProgramMemory.java index fd2080450..2d6758cad 100644 --- a/src/main/java/de/neemann/digital/core/memory/ProgramMemory.java +++ b/src/main/java/de/neemann/digital/core/memory/ProgramMemory.java @@ -22,4 +22,8 @@ public interface ProgramMemory { */ void setProgramMemory(DataField dataField); + /** + * @return the data bits + */ + int getDataBits(); } diff --git a/src/main/java/de/neemann/digital/core/memory/RAMInterface.java b/src/main/java/de/neemann/digital/core/memory/RAMInterface.java index 458432b91..20c69a40b 100644 --- a/src/main/java/de/neemann/digital/core/memory/RAMInterface.java +++ b/src/main/java/de/neemann/digital/core/memory/RAMInterface.java @@ -24,11 +24,6 @@ public interface RAMInterface extends ProgramMemory { */ int getSize(); - /** - * @return the data bits - */ - int getDataBits(); - /** * @return the addr bits */ diff --git a/src/main/java/de/neemann/digital/core/memory/ROM.java b/src/main/java/de/neemann/digital/core/memory/ROM.java index a3f4fdbf6..ebe4ff3f0 100644 --- a/src/main/java/de/neemann/digital/core/memory/ROM.java +++ b/src/main/java/de/neemann/digital/core/memory/ROM.java @@ -103,7 +103,7 @@ public class ROM extends Node implements Element, ROMInterface, ProgramMemory { public void init(Model model) throws NodeException { if (autoLoad) { try { - data = new DataField(hexFile); + data = DataFieldImporter.read(hexFile, dataBits); } catch (IOException e) { throw new NodeException(e.getMessage(), this, -1, null); } diff --git a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java index b4cc78c21..5f80aee34 100644 --- a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java +++ b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java @@ -8,7 +8,7 @@ package de.neemann.digital.gui; import de.neemann.digital.core.Model; import de.neemann.digital.core.Node; import de.neemann.digital.core.NodeException; -import de.neemann.digital.core.memory.DataField; +import de.neemann.digital.core.memory.DataFieldImporter; import de.neemann.digital.core.memory.ProgramMemory; import de.neemann.digital.lang.Lang; @@ -40,7 +40,8 @@ public class ProgramMemoryLoader implements ModelModifier { throw new NodeException(Lang.get("err_moreThenOneRomFound")); try { - ((ProgramMemory) progMem.get(0)).setProgramMemory(new DataField(romHex)); + final ProgramMemory memory = (ProgramMemory) progMem.get(0); + memory.setProgramMemory(DataFieldImporter.read(romHex, memory.getDataBits())); } catch (IOException e) { throw new NodeException(Lang.get("err_errorLoadingRomData"), e); } diff --git a/src/main/java/de/neemann/digital/gui/components/DataEditor.java b/src/main/java/de/neemann/digital/gui/components/DataEditor.java index e9515d988..4cf64e951 100644 --- a/src/main/java/de/neemann/digital/gui/components/DataEditor.java +++ b/src/main/java/de/neemann/digital/gui/components/DataEditor.java @@ -10,6 +10,7 @@ import de.neemann.digital.core.Model; import de.neemann.digital.core.ModelEvent; import de.neemann.digital.core.SyncAccess; import de.neemann.digital.core.memory.DataField; +import de.neemann.digital.core.memory.DataFieldImporter; import de.neemann.digital.gui.SaveAsHelper; import de.neemann.digital.lang.Lang; import de.neemann.gui.ErrorMessage; @@ -133,7 +134,7 @@ public class DataEditor extends JDialog { if (fc.showOpenDialog(DataEditor.this) == JFileChooser.APPROVE_OPTION) { fileName = fc.getSelectedFile(); try { - localDataField.setDataFrom(new DataField(fc.getSelectedFile())); + localDataField.setDataFrom(DataFieldImporter.read(fc.getSelectedFile(), dataBits)); dm.fireEvent(new TableModelEvent(dm)); } catch (IOException e1) { new ErrorMessage(Lang.get("msg_errorReadingFile")).addCause(e1).show(DataEditor.this); diff --git a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java index 520f71b9a..fd279d6b9 100644 --- a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java +++ b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java @@ -14,6 +14,7 @@ import de.neemann.digital.core.extern.Application; import de.neemann.digital.core.extern.PortDefinition; import de.neemann.digital.core.io.InValue; import de.neemann.digital.core.memory.DataField; +import de.neemann.digital.core.memory.DataFieldImporter; import de.neemann.digital.core.memory.ROM; import de.neemann.digital.core.memory.rom.ROMManger; import de.neemann.digital.draw.elements.PinException; @@ -627,9 +628,13 @@ public final class EditorFactory { @Override public void actionPerformed(ActionEvent e) { try { - data = new DataField(attr.getFile(ROM.LAST_DATA_FILE_KEY)); + getAttributeDialog().storeEditedValues(); + int dataBits = attr.get(Keys.BITS); + data = DataFieldImporter.read(attr.getFile(ROM.LAST_DATA_FILE_KEY), dataBits); } catch (IOException e1) { new ErrorMessage(Lang.get("msg_errorReadingFile")).addCause(e1).show(panel); + } catch (EditorParseException e1) { + new ErrorMessage(Lang.get("msg_invalidEditorValue")).addCause(e1).show(panel); } } } diff --git a/src/test/java/de/neemann/digital/core/memory/DataFieldImporterTest.java b/src/test/java/de/neemann/digital/core/memory/DataFieldImporterTest.java new file mode 100644 index 000000000..eec4baaf0 --- /dev/null +++ b/src/test/java/de/neemann/digital/core/memory/DataFieldImporterTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.memory; + +import junit.framework.TestCase; + +public class DataFieldImporterTest extends TestCase { + + public void testCreate16() { + DataField df = new DataField(16); + DataFieldImporter.DataArray a = DataFieldImporter.create(df, 16); + a.put(0, 0x01); + a.put(1, 0x10); + a.put(2, 0x77); + a.put(3, 0x33); + + assertEquals(0x1001, df.getDataWord(0)); + assertEquals(0x3377, df.getDataWord(1)); + } + + public void testCreate24() { + DataField df = new DataField(16); + DataFieldImporter.DataArray a = DataFieldImporter.create(df, 24); + a.put(0, 0x01); + a.put(1, 0x10); + a.put(2, 0x77); + a.put(3, 0x33); + + assertEquals(0x771001, df.getDataWord(0)); + assertEquals(0x33, df.getDataWord(1)); + } + + public void testCreate32() { + DataField df = new DataField(16); + DataFieldImporter.DataArray a = DataFieldImporter.create(df, 32); + a.put(0, 0x01); + a.put(1, 0x10); + a.put(2, 0x77); + a.put(3, 0x33); + a.put(4, 0x11); + + assertEquals(0x33771001, df.getDataWord(0)); + assertEquals(0x11, df.getDataWord(1)); + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/core/memory/IntelHexReaderTest.java b/src/test/java/de/neemann/digital/core/memory/IntelHexReaderTest.java new file mode 100644 index 000000000..84827cb85 --- /dev/null +++ b/src/test/java/de/neemann/digital/core/memory/IntelHexReaderTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.memory; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class IntelHexReaderTest extends TestCase { + + public void testReadHex() throws IOException { + String data = ":100000000C9438000C9442000C9442000C94420072\n" + + ":100010000C9442000C9442000C9442000C94420058\n" + + ":100020000C9442000C9442000C9442000C94420048\n" + + ":100030000C9442000C9442000C9442000C94420038\n" + + ":100040000C9442000C9442000C9442000C94420028\n" + + ":100050000C9442000C9442000C9442000C94420018\n" + + ":100060000C9442000C9442000C9442000C94420008\n" + + ":1000700011241FBECFEFD0E1DEBFCDBF0E944400F0\n" + + ":100080000C944A000C9400008FEF84B986B18095DF\n" + + ":0800900085B9FCCFF894FFCF05\n" + + ":00000001FF"; + + int[] bin = new int[200]; + new IntelHexReader(new ByteArrayInputStream(data.getBytes()), (addr, aByte) -> bin[addr] = aByte); + + assertEquals(0x0c, bin[0x00]); + assertEquals(0x11, bin[0x70]); + assertEquals(0x24, bin[0x71]); + assertEquals(0x00, bin[0x7F]); + assertEquals(0x95, bin[0x8F]); + assertEquals(0xCF, bin[0x97]); + } + + public void testReadSeg() throws IOException { + String data = ":020000020110EB\n" + + ":0800000085B9FCCFF894FFCF95\n"; + + int[] bin = new int[0x11010]; + new IntelHexReader(new ByteArrayInputStream(data.getBytes()), (addr, aByte) -> bin[addr] = aByte); + + assertEquals(0x00, bin[0x00]); + assertEquals(0x85, bin[0x1100]); + assertEquals(0xb9, bin[0x1101]); + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/integration/TestProcessor.java b/src/test/java/de/neemann/digital/integration/TestProcessor.java index 2069f74cc..7d9dcddf4 100644 --- a/src/test/java/de/neemann/digital/integration/TestProcessor.java +++ b/src/test/java/de/neemann/digital/integration/TestProcessor.java @@ -7,10 +7,7 @@ package de.neemann.digital.integration; import de.neemann.digital.core.Model; import de.neemann.digital.core.NodeException; -import de.neemann.digital.core.memory.DataField; -import de.neemann.digital.core.memory.RAMDualPort; -import de.neemann.digital.core.memory.RAMSinglePort; -import de.neemann.digital.core.memory.ROM; +import de.neemann.digital.core.memory.*; import de.neemann.digital.draw.elements.PinException; import de.neemann.digital.draw.library.ElementNotFoundException; import junit.framework.TestCase; @@ -37,7 +34,7 @@ public class TestProcessor extends TestCase { } assertNotNull(rom); - rom.setData(new DataField(new File(Resources.getRoot(), program))); + rom.setData(DataFieldImporter.read(new File(Resources.getRoot(), program), rom.getDataBits())); runner.getModel().init(true); return runner;