summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Reichel <sre@ring0.de>2011-12-12 00:11:34 +0100
committerSebastian Reichel <sre@ring0.de>2011-12-12 00:11:34 +0100
commit90c9a148a94ed92c93d42110e45d8787f7ac1eff (patch)
tree447e0ec7bfa5c60a87468851c2fff741d6e645f2
downloadlp5523-assembler-90c9a148a94ed92c93d42110e45d8787f7ac1eff.tar.bz2
initial import
-rw-r--r--Makefile15
-rw-r--r--README94
-rw-r--r--assembler.vala140
-rw-r--r--lp5523.vala168
-rw-r--r--test.vala141
5 files changed, 558 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5ff359d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+all: lp5523-as
+
+test: lp5523-test
+ @./$<
+
+lp5523-as: lp5523.vala assembler.vala
+ @valac-0.14 --enable-experimental -o $@ $^
+
+lp5523-test: lp5523.vala test.vala
+ @valac-0.14 --enable-experimental -o $@ $^
+
+clean:
+ @rm -f lp5523-as lp5523-test
+
+.PHONY: all test clean
diff --git a/README b/README
new file mode 100644
index 0000000..a75e2e3
--- /dev/null
+++ b/README
@@ -0,0 +1,94 @@
+This is a simple assembler for lp5523 led chips. You
+can use it to convert the microcode from lp5523 engines
+into human readable code and the other way around.
+
+------------------------------------------------------------
+The Assembly Language
+------------------------------------------------------------
+
+The assembly language consists of the following mnemonics:
+
+ * start
+ This must be specified at the beginning of each program.
+
+ * restart
+ Jumps back to the start.
+
+ * stop
+ Stops the engine.
+
+ * stopr
+ Stops the engine with reset.
+
+ * stopi
+ Stops the engine with interrupt.
+
+ * stopri
+ Stops the engine with reset & interrupt.
+
+ * send1, send2, send3
+ Sends signal to engine 1, 2 or 3.
+
+ * wait1, wait2, wait3
+ Waits for signal from engine 1, 2 or 3.
+
+ * set LEVEL
+ Sets output to LEVEL (0-255).
+
+ * sleep TIME
+ - TIME can be between 0 and 61
+ Does nothing for the specified amount of time. See
+ below for TIME value -> time in seconds calculation.
+
+ * fade TIME +STEPS
+ - TIME can be between 0 and 61
+ - STEPS can be between 0 and 255
+ - The '+' is important
+ Slowly increase the output by STEPS in the specified
+ amount of time.
+
+ * fade TIME -STEPS
+ - TIME can be between 0 and 61
+ - STEPS can be between 0 and 255
+ - The '-' is important
+ Slowly decrease the output by STEPS in the specified
+ amount of time.
+
+ * branch COUNT PC
+ - COUNT can be between 0 and 63
+ - PC can be between 0 and 95
+ Jumps (back) COUNT times to the opcode located at PC.
+ A COUNT value of 0 is interpreted as infinite.
+
+
+Calculation of time in ms for a given TIME value:
+
+ if(value < 31)
+ return value * 0.49ms;
+ else
+ return (value-31) * 15.6ms;
+
+--------------------------------------------------------
+Usage
+--------------------------------------------------------
+
+Assemble lp5523.S into lp5523 machine code:
+ $ lp5523-as file.S > result.txt
+ $ echo "code" | lp5523-as > result.txt
+
+Disassemble lp5523 machine code in assembly code:
+ $ lp5523-as -d input > result.txt
+ $ echo "code" | lp5523-as -d > result.txt
+
+--------------------------------------------------------
+Building & Installation
+--------------------------------------------------------
+
+Building:
+ $ make
+
+Running Unit Tests:
+ $ make test
+
+Installation:
+ $ install -m755 lp5523 /usr/local/bin
diff --git a/assembler.vala b/assembler.vala
new file mode 100644
index 0000000..e44ceb8
--- /dev/null
+++ b/assembler.vala
@@ -0,0 +1,140 @@
+/* assembler.vala
+ * Copyright 2011, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+const OptionEntry[] option_entries = {
+ {"assemble", 'a', OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) parse_mode, "use assemble mode", null},
+ {"disassemble", 'd', OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) parse_mode, "use disassemble mode", null},
+ { "", 0, 0, OptionArg.FILENAME_ARRAY, ref files, "input file", "FILE" },
+ {null}
+};
+
+enum mode {
+ ASSEMBLE,
+ DISASSEMBLE
+}
+
+static string[] files;
+static mode m;
+
+bool parse_mode(string key, string? val) throws OptionError {
+ switch(key) {
+ case "--assemble":
+ case "-a":
+ m = mode.ASSEMBLE;
+ return true;
+ case "--disassemble":
+ case "-d":
+ m = mode.DISASSEMBLE;
+ return true;
+ default:
+ throw new OptionError.UNKNOWN_OPTION("Unknown Option " + key);
+ }
+}
+
+string assemble(FileStream stream) throws lp5523.ParsingError {
+ string line;
+ string result = "";
+
+ /* read until EOF */
+ while((line = stream.read_line()) != null) {
+ /* skip comment lines */
+ line = line.strip();
+ if(line.has_prefix("#"))
+ continue;
+
+ /* remove inline comments */
+ line = line.split("#", 2)[0];
+ line = line.strip();
+
+ result += "%04hx".printf(lp5523.assemble(line));
+ }
+
+ /* size check */
+ if(result.length/4 > lp5523.PROGSPACE_SIZE)
+ warning("this does not fit into lp5523 progspace");
+
+ result += "\n";
+
+ return result;
+}
+
+public string disassemble(FileStream stream) throws lp5523.ParsingError {
+ string line;
+ string result = "";
+
+ line = stream.read_line();
+
+ if(line == null)
+ return "";
+
+ /* remove trailing newline */
+ if(line.length % 2 == 1 && line.has_suffix("\n"))
+ line = line.substring(0, line.length-1);
+
+ /* disassemble */
+ for(int i=0; i<line.length/4; i++) {
+ var cmd = line.substring(i*4, 4);
+ uint16 opcode;
+ cmd.scanf("%04hx", out opcode);
+ result += lp5523.disassemble(opcode) + "\n";
+ }
+
+ return result;
+}
+
+int main(string[] args) {
+ unowned FileStream stream;
+ FileStream file;
+
+ /* parse parameters from shell */
+ var context = new OptionContext("- lp5523 (dis-)assembler");
+ context.set_help_enabled(true);
+ context.add_main_entries(option_entries, "lp5523-as");
+
+ try {
+ context.parse(ref args);
+ } catch(OptionError e) {
+ stderr.puts(e.message + "\n");
+ return 1;
+ }
+
+ /* open file or stdin respectively */
+ if(files == null || files[0] == "-") {
+ stream = stdin;
+ } else {
+ file = FileStream.open(files[0], "r");
+
+ if(file == null) {
+ stderr.printf("Could not open '%s'.\n", files[0]);
+ return 1;
+ }
+
+ stream = file;
+ }
+
+ /* do the actual work */
+ try {
+ if(m == mode.ASSEMBLE)
+ stdout.puts(assemble(stream));
+ else if(m == mode.DISASSEMBLE)
+ stdout.puts(disassemble(stream));
+ } catch(Error e) {
+ stderr.puts(e.message + "\n");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/lp5523.vala b/lp5523.vala
new file mode 100644
index 0000000..f6d4c64
--- /dev/null
+++ b/lp5523.vala
@@ -0,0 +1,168 @@
+/* lp5523.vala
+ * Copyright 2011, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+namespace lp5523 {
+ public errordomain ParsingError {
+ INVALID_OPCODE,
+ INVALID_COMMAND,
+ OUT_OF_RANGE
+ }
+
+ public uint8 PROGSPACE_SIZE = 96;
+
+ public string disassemble(uint16 opcode) throws ParsingError {
+ opcode = opcode.to_big_endian();
+ uint8 byte1 = (uint8) opcode;
+ uint8 byte2 = (uint8) (opcode >> 8);
+ opcode = uint16.from_big_endian(opcode);
+
+ if(byte1 >= 0x02 && byte1 <= 0x3f) {
+ bool neg = (byte1 & 0x01) == 0x01;
+ uint8 time = (byte1 >> 1) - 0x01;
+ uint8 steps = byte2;
+ if(neg && steps == 0x00)
+ return "sleep %hhu".printf(time);
+ else
+ return "fade %hhu %c%hhu".printf(time, neg ? '-' : '+', steps);
+ } else if(byte1 == 0x40) {
+ return "set %hhu".printf(byte2);
+ } else if(byte1 >= 0x42 && byte1 <= 0x7f) {
+ bool neg = (byte1 & 0x01) == 0x01;
+ uint8 time = (byte1 >> 1) - 0x02;
+ uint8 steps = byte2;
+ if(neg && steps == 0x00)
+ return "sleep %hhu".printf(time);
+ else
+ return "fade %hhu %c%hhu".printf(time, neg ? '-' : '+', steps);
+ } else if(byte1 >= 0xa0 && byte1 <= 0xbf) {
+ uint8 count = (byte1 << 1) & ~0xc1;
+ if((byte2 & 0x80) == 0x80) count++;
+ uint8 pc = byte2 & ~0x80;
+ return "branch %hhu %hhu".printf(count, pc);
+ }
+
+ switch(opcode) {
+ case 0x0000: return "restart";
+ case 0x9d80: return "start";
+ case 0xc000: return "stop";
+ case 0xc800: return "stopr";
+ case 0xd000: return "stopi";
+ case 0xd800: return "stopri";
+ case 0xe002: return "send1";
+ case 0xe004: return "send2";
+ case 0xe008: return "send3";
+ case 0xe080: return "wait1";
+ case 0xe100: return "wait2";
+ case 0xe200: return "wait3";
+ default:
+ throw new ParsingError.INVALID_OPCODE("invalid opcode: %04hx".printf(opcode));
+ }
+ }
+
+ public uint16 assemble(string cmd) throws ParsingError {
+ MatchInfo m;
+
+ Regex REGEX_SET = /^set\s+(\d+)$/;
+ Regex REGEX_SLEEP = /^sleep\s+(\d+)$/;
+ Regex REGEX_FADE = /^fade\s+(\d+)\s+([\+\-])(\d+)$/;
+ Regex REGEX_BRANCH = /^branch\s+(\d+)\s+(\d+)$/;
+
+ if(REGEX_SLEEP.match(cmd, 0, out m)) {
+ uint16 time = (uint8) int.parse(m.fetch(1));
+
+ if(time < 31)
+ time += 0x01;
+ else if(time < 62)
+ time += 0x02;
+ else
+ throw new ParsingError.OUT_OF_RANGE("fade time out of range (0-61)");
+
+ time <<= 1;
+ time |= 0x01;
+ time <<= 8;
+
+ return time;
+ }
+
+ if(REGEX_FADE.match(cmd, 0, out m)) {
+ uint16 time = (uint8) int.parse(m.fetch(1));
+ bool neg = m.fetch(2).get(0) == '-';
+ uint8 steps = (uint8) int.parse(m.fetch(3));
+
+ if(time < 31)
+ time += 0x01;
+ else if(time < 62)
+ time += 0x02;
+ else
+ throw new ParsingError.OUT_OF_RANGE("fade time out of range (0-61)");
+
+ time <<= 1;
+
+ if(neg)
+ time |= 0x01;
+
+ time <<= 8;
+
+ return time + steps;
+ }
+
+ if(REGEX_SET.match(cmd, 0, out m)) {
+ uint16 byte1 = 0x4000;
+
+ uint level = int.parse(m.fetch(1));
+ if(level > 0xFF)
+ throw new ParsingError.OUT_OF_RANGE("brightness level is out of range (0-255)");
+
+ return byte1 + (uint8) level;
+ }
+
+ if(REGEX_BRANCH.match(cmd, 0, out m)) {
+ uint16 byte1 = (uint8) int.parse(m.fetch(1));
+ uint8 byte2 = (uint8) int.parse(m.fetch(2));
+
+ if((byte1 & 0x01) != 0x00)
+ byte2 |= 0x80;
+ else
+ byte2 &= ~0x80;
+
+ byte1 >>= 1; /* shift 1 right */
+ byte1 |= 0x80; /* set 1st bit */
+ byte1 &= ~0x40; /* clear 2nd bit */
+ byte1 |= 0x20; /* set 3rd bit */
+
+ byte1 <<= 8;
+
+ return byte1 + byte2;
+ }
+
+ switch(cmd) {
+ case "start": return 0x9d80;
+ case "restart": return 0x0000;
+ case "stop": return 0xc000;
+ case "stopr": return 0xc800;
+ case "stopi": return 0xd000;
+ case "stopri": return 0xd800;
+ case "send1": return 0xe002;
+ case "send2": return 0xe004;
+ case "send3": return 0xe008;
+ case "wait1": return 0xe080;
+ case "wait2": return 0xe100;
+ case "wait3": return 0xe200;
+ default:
+ throw new ParsingError.INVALID_COMMAND("invalid command: %s".printf(cmd));
+ }
+ }
+}
diff --git a/test.vala b/test.vala
new file mode 100644
index 0000000..29fd902
--- /dev/null
+++ b/test.vala
@@ -0,0 +1,141 @@
+/* test.vala
+ * Copyright 2011, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+public class AssemblerTestSuite {
+ public static void add_simple_assembly_tests() {
+ /* start */
+ Test.add_func("/lp5523/reassembly/restart", () => {
+ simple_assembly_test("restart", 0x0000);
+ });
+
+ Test.add_func("/lp5523/reassembly/start", () => {
+ simple_assembly_test("start", 0x9d80);
+ });
+
+ /* stop */
+ Test.add_func("/lp5523/reassembly/stop", () => {
+ simple_assembly_test("stop", 0xc000);
+ });
+
+ Test.add_func("/lp5523/reassembly/stopi", () => {
+ simple_assembly_test("stopr", 0xc800);
+ });
+
+ Test.add_func("/lp5523/reassembly/stopr", () => {
+ simple_assembly_test("stopi", 0xd000);
+ });
+
+ Test.add_func("/lp5523/reassembly/stopri", () => {
+ simple_assembly_test("stopri", 0xd800);
+ });
+
+ /* signals */
+ Test.add_func("/lp5523/reassembly/send1", () => {
+ simple_assembly_test("send1", 0xe002);
+ });
+
+ Test.add_func("/lp5523/reassembly/send2", () => {
+ simple_assembly_test("send2", 0xe004);
+ });
+
+ Test.add_func("/lp5523/reassembly/send3", () => {
+ simple_assembly_test("send3", 0xe008);
+ });
+
+ Test.add_func("/lp5523/reassembly/wait1", () => {
+ simple_assembly_test("wait1", 0xe080);
+ });
+
+ Test.add_func("/lp5523/reassembly/wait2", () => {
+ simple_assembly_test("wait2", 0xe100);
+ });
+
+ Test.add_func("/lp5523/reassembly/wait3", () => {
+ simple_assembly_test("wait3", 0xe200);
+ });
+
+ /* fading */
+ Test.add_func("/lp5523/reassembly/fade 1 +255", () => {
+ simple_assembly_test("fade 1 +255", 0x04ff);
+ });
+
+ Test.add_func("/lp5523/reassembly/fade 0 -100", () => {
+ simple_assembly_test("fade 0 -100", 0x0364);
+ });
+
+ Test.add_func("/lp5523/reassembly/fade 61 -100", () => {
+ simple_assembly_test("fade 61 -100", 0x7f64);
+ });
+
+ Test.add_func("/lp5523/reassembly/fade 61 +100", () => {
+ simple_assembly_test("fade 61 +100", 0x7e64);
+ });
+
+ Test.add_func("/lp5523/reassembly/fade 0 +100", () => {
+ simple_assembly_test("fade 0 +100", 0x0264);
+ });
+
+ Test.add_func("/lp5523/reassembly/fade 1 -255", () => {
+ simple_assembly_test("fade 1 -255", 0x05ff);
+ });
+
+ /* sleeping */
+ Test.add_func("/lp5523/reassembly/sleep 61", () => {
+ simple_assembly_test("sleep 61", 0x7f00);
+ });
+
+ /* instant brightness setting */
+ Test.add_func("/lp5523/reassembly/set 255", () => {
+ simple_assembly_test("set 255", 0x40ff);
+ });
+
+ Test.add_func("/lp5523/reassembly/set 0", () => {
+ simple_assembly_test("set 0", 0x4000);
+ });
+
+ /* branching */
+ Test.add_func("/lp5523/reassembly/branch 0 0", () => {
+ simple_assembly_test("branch 0 0", 0xa000);
+ });
+
+ Test.add_func("/lp5523/reassembly/branch 63 95", () => {
+ simple_assembly_test("branch 63 95", 0xbfdf);
+ });
+ }
+
+ public static void simple_assembly_test(string cmd, uint16 opcode) {
+ try {
+ uint16 assembled_opcode = lp5523.assemble(cmd);
+ string disassembled_cmd = lp5523.disassemble(opcode);
+
+ if(assembled_opcode != opcode) {
+ error("\"%04hx\" != \"%04hx\"", opcode, assembled_opcode);
+ }
+
+ if(disassembled_cmd != cmd) {
+ error("\"%s\" != \"%s\"", cmd, disassembled_cmd);
+ }
+ } catch(lp5523.ParsingError e) {
+ error("Received Parsing Error: %s", e.message);
+ }
+ }
+}
+
+void main(string[] args) {
+ Test.init(ref args);
+ AssemblerTestSuite.add_simple_assembly_tests();
+ Test.run();
+}