summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Schodet2010-12-24 18:50:48 +0100
committerNicolas Schodet2010-12-24 18:50:48 +0100
commit2f33fc36b3aa6b53d7e0b1827401303ae7d67c02 (patch)
tree4a0ff98b596948e71d4cc570e31a60b2ed70aa9d
parent948c655985dcbe6f8abcc7da850b91e8caa73080 (diff)
digital/avr/common: add bus macros
-rw-r--r--digital/avr/common/io_bus.h159
-rw-r--r--digital/avr/common/test/Makefile18
-rw-r--r--digital/avr/common/test/test_io_bus.c230
3 files changed, 407 insertions, 0 deletions
diff --git a/digital/avr/common/io_bus.h b/digital/avr/common/io_bus.h
new file mode 100644
index 00000000..64d3e314
--- /dev/null
+++ b/digital/avr/common/io_bus.h
@@ -0,0 +1,159 @@
+#ifndef io_bus_h
+#define io_bus_h
+/* io_bus.h */
+/* avr.modules - AVR modules. {{{
+ *
+ * Copyright (C) 2010 Nicolas Schodet
+ *
+ * APBTeam:
+ * Web: http://apbteam.org/
+ * Email: team AT apbteam DOT org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * }}} */
+#include "preproc.h"
+#include "io.h"
+
+/** Macro to work with buses of several AVR IO.
+ *
+ * A bus is defined by one or several IO blocks. Each IO block is defined by
+ * its port, width and shift. Here is an example:
+ *
+ * A, 4, 0, B, 4, 2
+ *
+ * This defines a bus composed of two blocks. The first block is composed of
+ * the first four IO from port A, and the second one is composed of the IO 2,
+ * 3, 4 and 5. The bus have the following mapping:
+ *
+ * 0 => A0 (LSB)
+ * 1 => A1
+ * 2 => A2
+ * 3 => A3
+ * 4 => B2
+ * 5 => B3
+ * 6 => B4
+ * 7 => B5 (MSB)
+ *
+ * Once a bus is defined, it can be accessed as a whole using macros from this
+ * file:
+ *
+ * IO_BUS_OUPUT (MY_BUS);
+ * IO_BUS_SET (MY_BUS, 42);
+ * IO_BUS_SET (MY_BUS, 0);
+ * IO_BUS_INPUT (MY_BUS)
+ * v = IO_BUS_GET (MY_BUS);
+ *
+ * Width and shift must expand to a single integer. Currently limited to 3
+ * blocks, that can be easily changed. Total bus width is limited to 8 bit
+ * (because of uint8_t temporary storage needed for right shift). */
+
+/* Warning: C preprocessor tricks inside. */
+
+/* Internal: Return a bit mask for the given width. */
+#define IO_BUS_ONES(w) ((1u << (w)) - 1)
+
+/* Internal: Return a bit mask for the given width and shift. */
+#define IO_BUS_MASK(w, s) (IO_BUS_ONES ((w)) << (s))
+
+/* Internal: Replace bits in old 8 bit register with value from new at the
+ * given width and shift. This is done to optimize the case when the whole
+ * register is replaced and does not have to be read (the compiler can not
+ * optimize that because registers are volatile). Width must expand to a
+ * single integer. */
+#define IO_BUS_REPLACE(old, new, w, s) \
+ PREPROC_PASTE (IO_BUS_REPLACE_, w) ((old), (new), (s))
+#define IO_BUS_REPLACE_n(old, new, w, s) \
+ (((old) & ~IO_BUS_MASK ((w), (s))) \
+ | (((new) & IO_BUS_ONES ((w))) << (s)))
+#define IO_BUS_REPLACE_1(old, new, s) IO_BUS_REPLACE_n ((old), (new), 1, (s))
+#define IO_BUS_REPLACE_2(old, new, s) IO_BUS_REPLACE_n ((old), (new), 2, (s))
+#define IO_BUS_REPLACE_3(old, new, s) IO_BUS_REPLACE_n ((old), (new), 3, (s))
+#define IO_BUS_REPLACE_4(old, new, s) IO_BUS_REPLACE_n ((old), (new), 4, (s))
+#define IO_BUS_REPLACE_5(old, new, s) IO_BUS_REPLACE_n ((old), (new), 5, (s))
+#define IO_BUS_REPLACE_6(old, new, s) IO_BUS_REPLACE_n ((old), (new), 6, (s))
+#define IO_BUS_REPLACE_7(old, new, s) IO_BUS_REPLACE_n ((old), (new), 7, (s))
+#define IO_BUS_REPLACE_8(old, new, s) (new)
+
+/** Read value from bus. */
+#define IO_BUS_GET(bus) \
+ PREPROC_NARG_CALL (IO_BUS_GET_, bus)
+#define IO_BUS_GET_3(p0, w0, s0) \
+ ((PIN ## p0 >> (s0)) & IO_BUS_ONES ((w0)))
+#define IO_BUS_GET_6(p0, w0, s0, p1, w1, s1) \
+ (IO_BUS_GET_3 (p0, (w0), (s0)) \
+ | (IO_BUS_GET_3 (p1, (w1), (s1)) << (w0)))
+#define IO_BUS_GET_9(p0, w0, s0, p1, w1, s1, p2, w2, s2) \
+ (IO_BUS_GET_6 (p0, (w0), (s0), p1, (w1), (s1)) \
+ | (IO_BUS_GET_3 (p2, (w2), (s2)) << ((w0) + (w1))))
+
+/** Write value to bus. */
+#define IO_BUS_SET(bus, value) \
+ PREPROC_NARG_CALL (IO_BUS_SET_, bus, (value))
+#define IO_BUS_SET_4(p0, w0, s0, value) \
+ do { \
+ uint8_t _value1 = (value); \
+ PORT ## p0 = IO_BUS_REPLACE (PORT ## p0, _value1, w0, (s0)); \
+ } while (0)
+#define IO_BUS_SET_7(p0, w0, s0, p1, w1, s1, value) \
+ do { \
+ uint8_t _value2 = (value); \
+ IO_BUS_SET_4 (p0, w0, (s0), _value2); \
+ IO_BUS_SET_4 (p1, w1, (s1), _value2 >> (w0)); \
+ } while (0)
+#define IO_BUS_SET_10(p0, w0, s0, p1, w1, s1, p2, w2, s2, value) \
+ do { \
+ uint8_t _value3 = (value); \
+ IO_BUS_SET_7 (p0, w0, (s0), p1, w1, (s1), _value3); \
+ IO_BUS_SET_4 (p2, w2, (s2), _value3 >> ((w0) + (w1))); \
+ } while (0)
+
+/** Set bus as input. */
+#define IO_BUS_INPUT(bus) \
+ PREPROC_NARG_CALL (IO_BUS_INPUT_, bus)
+#define IO_BUS_INPUT_3(p0, w0, s0) \
+ do { \
+ DDR ## p0 = IO_BUS_REPLACE (DDR ## p0, 0, w0, (s0)); \
+ } while (0)
+#define IO_BUS_INPUT_6(p0, w0, s0, p1, w1, s1) \
+ do { \
+ IO_BUS_INPUT_3 (p0, w0, (s0)); \
+ IO_BUS_INPUT_3 (p1, w1, (s1)); \
+ } while (0)
+#define IO_BUS_INPUT_9(p0, w0, s0, p1, w1, s1, p2, w2, s2) \
+ do { \
+ IO_BUS_INPUT_6 (p0, w0, (s0), p1, w1, (s1)); \
+ IO_BUS_INPUT_3 (p2, w2, (s2)); \
+ } while (0)
+
+/** Set bus as output. */
+#define IO_BUS_OUTPUT(bus) \
+ PREPROC_NARG_CALL (IO_BUS_OUTPUT_, bus)
+#define IO_BUS_OUTPUT_3(p0, w0, s0) \
+ do { \
+ DDR ## p0 = IO_BUS_REPLACE (DDR ## p0, IO_BUS_ONES (w0), w0, (s0)); \
+ } while (0)
+#define IO_BUS_OUTPUT_6(p0, w0, s0, p1, w1, s1) \
+ do { \
+ IO_BUS_OUTPUT_3 (p0, w0, (s0)); \
+ IO_BUS_OUTPUT_3 (p1, w1, (s1)); \
+ } while (0)
+#define IO_BUS_OUTPUT_9(p0, w0, s0, p1, w1, s1, p2, w2, s2) \
+ do { \
+ IO_BUS_OUTPUT_6 (p0, w0, (s0), p1, w1, (s1)); \
+ IO_BUS_OUTPUT_3 (p2, w2, (s2)); \
+ } while (0)
+
+#endif /* io_bus_h */
diff --git a/digital/avr/common/test/Makefile b/digital/avr/common/test/Makefile
new file mode 100644
index 00000000..b4f7e3db
--- /dev/null
+++ b/digital/avr/common/test/Makefile
@@ -0,0 +1,18 @@
+BASE = ../..
+PROGS = test_io_bus
+ASM_FILES = test_asm_bus.lst test_asm_manual.lst
+EXTRA_CLEAN_FILES = $(ASM_FILES)
+test_io_bus_SOURCES = test_io_bus.c
+MODULES = math/random
+CONFIGFILE = /dev/null
+AVR_MCU = atmega128
+# -O2 : speed
+# -Os : size
+OPTIMIZE =
+
+include $(BASE)/make/Makefile.gen
+
+avr: $(ASM_FILES)
+test_asm_%.lst: test_io_bus.avr.lst
+ sed -ne '/test_asm_$*>:$$/,/ret$$/p' < $< \
+ | sed -e 's/^[ 0-9a-f]\{4\}:\t[ 0-9a-f]\{5\}/\t/' > $@
diff --git a/digital/avr/common/test/test_io_bus.c b/digital/avr/common/test/test_io_bus.c
new file mode 100644
index 00000000..501950a7
--- /dev/null
+++ b/digital/avr/common/test/test_io_bus.c
@@ -0,0 +1,230 @@
+/* test_io_bus.c */
+/* avr.modules - AVR modules. {{{
+ *
+ * Copyright (C) 2010 Nicolas Schodet
+ *
+ * APBTeam:
+ * Web: http://apbteam.org/
+ * Email: team AT apbteam DOT org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * }}} */
+#include "common.h"
+#include "io_bus.h"
+
+#include "modules/math/random/random.h"
+
+#include <stdio.h>
+
+/* Use non existents ports. */
+#define TEST_PORTS 12
+volatile uint8_t PORTU[TEST_PORTS], DDRU[TEST_PORTS], PINU[TEST_PORTS];
+
+#define TEST_BUSES 7
+#define TEST_SLOTS 11
+#define TEST_BUS_0 U[0], 4, 0
+#define TEST_BUS_1 U[1], 4, 2
+#define TEST_BUS_2 U[2], 4, 4
+#define TEST_BUS_3 U[3], 4, 0, U[4], 4, 0
+#define TEST_BUS_4 U[5], 4, 0, U[6], 2, 0, U[7], 2, 6
+#define TEST_BUS_5 U[8], 3, 1, U[9], 2, 0, U[10], 3, 0
+#define TEST_BUS_6 U[11], 8, 0
+
+#define TEST_OUTER_LOOPS 10
+#define TEST_INNER_LOOPS 10
+
+/* Table of unused bit masks. */
+static const uint8_t unused_mask[TEST_PORTS] = { 0xf0, 0xc3, 0x0f, 0xf0, 0xf0,
+ 0xf0, 0xfc, 0x3f, 0xf1, 0xfc, 0xf8, 0x00 };
+
+/* Make a test iteration, set input or output, rotate bus values. */
+void
+test_io_bus_iteration (uint8_t out)
+{
+ if (out)
+ {
+ IO_BUS_OUTPUT (TEST_BUS_0);
+ IO_BUS_OUTPUT (TEST_BUS_1);
+ IO_BUS_OUTPUT (TEST_BUS_2);
+ IO_BUS_OUTPUT (TEST_BUS_3);
+ IO_BUS_OUTPUT (TEST_BUS_4);
+ IO_BUS_OUTPUT (TEST_BUS_5);
+ IO_BUS_OUTPUT (TEST_BUS_6);
+ }
+ else
+ {
+ IO_BUS_INPUT (TEST_BUS_0);
+ IO_BUS_INPUT (TEST_BUS_1);
+ IO_BUS_INPUT (TEST_BUS_2);
+ IO_BUS_INPUT (TEST_BUS_3);
+ IO_BUS_INPUT (TEST_BUS_4);
+ IO_BUS_INPUT (TEST_BUS_5);
+ IO_BUS_INPUT (TEST_BUS_6);
+ }
+ IO_BUS_SET (TEST_BUS_0, IO_BUS_GET (TEST_BUS_1));
+ IO_BUS_SET (TEST_BUS_1, IO_BUS_GET (TEST_BUS_2));
+ IO_BUS_SET (TEST_BUS_2, IO_BUS_GET (TEST_BUS_3) & 0xf);
+ IO_BUS_SET (TEST_BUS_3, IO_BUS_GET (TEST_BUS_3) >> 4 | (IO_BUS_GET (TEST_BUS_4) & 0xf) << 4);
+ IO_BUS_SET (TEST_BUS_4, IO_BUS_GET (TEST_BUS_4) >> 4 | (IO_BUS_GET (TEST_BUS_5) & 0xf) << 4);
+ IO_BUS_SET (TEST_BUS_5, IO_BUS_GET (TEST_BUS_5) >> 4 | (IO_BUS_GET (TEST_BUS_6) & 0xf) << 4);
+ IO_BUS_SET (TEST_BUS_6, IO_BUS_GET (TEST_BUS_6) >> 4 | IO_BUS_GET (TEST_BUS_0) << 4);
+}
+
+unsigned long
+test_io_bus (void)
+{
+ uint8_t i, j, k;
+ unsigned long failed = 0;
+ for (k = 0; k < TEST_PORTS; k++)
+ PORTU[k] = DDRU[k] = PINU[k] = 0;
+ for (i = 0; i < TEST_OUTER_LOOPS; i++)
+ {
+ uint8_t buses[TEST_BUSES];
+ /* Randomize buses. */
+ for (k = 0; k < TEST_BUSES; k++)
+ buses[k] = random_u32 () & 0xff;
+ IO_BUS_SET (TEST_BUS_0, buses[0]);
+ buses[0] &= 0xf;
+ IO_BUS_SET (TEST_BUS_1, buses[1]);
+ buses[1] &= 0xf;
+ IO_BUS_SET (TEST_BUS_2, buses[2]);
+ buses[2] &= 0xf;
+ IO_BUS_SET (TEST_BUS_3, buses[3]);
+ IO_BUS_SET (TEST_BUS_4, buses[4]);
+ IO_BUS_SET (TEST_BUS_5, buses[5]);
+ IO_BUS_SET (TEST_BUS_6, buses[6]);
+ for (j = 0; j < TEST_SLOTS * TEST_OUTER_LOOPS; j++)
+ {
+ /* Randomize unused ports. */
+ uint8_t ports[TEST_PORTS];
+ for (k = 0; k < TEST_PORTS; k++)
+ {
+ ports[k] = random_u32 ();
+ PORTU[k] = (PORTU[k] & ~unused_mask[k])
+ | (ports[k] & unused_mask[k]);
+ PINU[k] = PORTU[k];
+ DDRU[k] = ports[k];
+ }
+ /* Rotate. */
+ test_io_bus_iteration (j & 1);
+ for (k = 0; k < TEST_PORTS; k++)
+ PINU[k] = PORTU[k];
+ /* Check direction. */
+ for (k = 0; k < TEST_PORTS; k++)
+ if (DDRU[k] != (0xff & (((j & 1) ? ~unused_mask[k] : 0)
+ | (ports[k] & unused_mask[k]))))
+ failed++;
+ /* Check unused ports. */
+ for (k = 0; k < TEST_PORTS; k++)
+ if ((PORTU[k] & unused_mask[k])
+ != (ports[k] & unused_mask[k]))
+ failed++;
+ }
+ /* Check buses. */
+ uint8_t get;
+ get = IO_BUS_GET (TEST_BUS_0);
+ if (get != buses[0])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_1);
+ if (get != buses[1])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_2);
+ if (get != buses[2])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_3);
+ if (get != buses[3])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_4);
+ if (get != buses[4])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_5);
+ if (get != buses[5])
+ failed++;
+ get = IO_BUS_GET (TEST_BUS_6);
+ if (get != buses[6])
+ failed++;
+ }
+ return failed;
+}
+
+/* Use real ports for assembly comparison. */
+#ifdef HOST
+volatile uint8_t PORTA, PORTB, DDRA, DDRB, PINA, PINB;
+#endif
+#define TEST_BUS_A1 A, 8, 0
+#define TEST_BUS_A2 A, 4, 0, B, 4, 0
+#define TEST_BUS_A3 A, 4, 2, B, 2, 4
+
+/* This is not supposed to be run, used to compare assembly with next
+ * function. */
+uint8_t
+test_asm_bus (uint8_t n)
+{
+ IO_BUS_OUTPUT (TEST_BUS_A1);
+ IO_BUS_OUTPUT (TEST_BUS_A2);
+ IO_BUS_OUTPUT (TEST_BUS_A3);
+ IO_BUS_INPUT (TEST_BUS_A1);
+ IO_BUS_INPUT (TEST_BUS_A2);
+ IO_BUS_INPUT (TEST_BUS_A3);
+ IO_BUS_SET (TEST_BUS_A1, n);
+ IO_BUS_SET (TEST_BUS_A2, n);
+ IO_BUS_SET (TEST_BUS_A3, n);
+ IO_BUS_SET (TEST_BUS_A1, 42);
+ IO_BUS_SET (TEST_BUS_A2, 42);
+ IO_BUS_SET (TEST_BUS_A2, 2);
+ IO_BUS_SET (TEST_BUS_A3, 42);
+ uint8_t v = IO_BUS_GET (TEST_BUS_A1);
+ v ^= IO_BUS_GET (TEST_BUS_A2);
+ v ^= IO_BUS_GET (TEST_BUS_A3);
+ return v;
+}
+
+/* This is not supposed to be run, used to compare assembly with previous
+ * function. */
+uint8_t
+test_asm_manual (uint8_t n)
+{
+ DDRA = 0xff;
+ DDRA |= 0x0f; DDRB |= 0x0f;
+ DDRA |= 0x3c; DDRB |= 0x30;
+ DDRA = 0x00;
+ DDRA &= 0xf0; DDRB &= 0xf0;
+ DDRA &= 0xc3; DDRB &= 0xcf;
+ PORTA = n;
+ PORTA = (PORTA & 0xf0) | (n & 0x0f);
+ PORTB = (PORTB & 0xf0) | (n >> 4 & 0x0f);
+ PORTA = (PORTA & 0xc3) | (n & 0x0f) << 2;
+ PORTB = (PORTB & 0xcf) | (n & 0x30);
+ PORTA = 42;
+ PORTA = (PORTA & 0xf0) | (42 & 0x0f);
+ PORTB = (PORTB & 0xf0) | (42 & 0xf0) >> 4;
+ PORTA = (PORTA & 0xf0) | (2 & 0x0f);
+ PORTB = (PORTB & 0xf0) | (2 & 0xf0) >> 4;
+ PORTA = (PORTA & 0xc3) | (42 & 0x0f) << 2;
+ PORTB = (PORTB & 0xcf) | (42 & 0x30);
+ uint8_t v = PINA;
+ v ^= (PINA & 0xf) | (PINB & 0xf) << 4;
+ v ^= (PINA & 0x3c) >> 2 | (PINB & 0x30);
+ return v;
+}
+
+int
+main (void)
+{
+ unsigned long failed = test_io_bus ();
+ printf ("failed %lu\n", failed);
+ return failed ? 1 : 0;
+}