Da waren in dem zdiw-Bytecode aus dem vorigen Abschnitt noch Fehler drin und ich habe zwei/drei umgestellt, weil die ausser der Reihe platziert waren.
Dafuer gibt's jetzt auch ein lauffaehiges Hallo-Welt-Programm.
Den vorigen Abschnitt (nach der #-Trennlinie) lasse ich stehen, weil ich keine Lust habe die anderen Infos nochmal zu tippen bzw. hier einzufuegen. :-)

Zusammenbau und Test des folgenden:

1. lua genzdiw.lua >zdiwdef.h

erzeugt die Bytecode-CPU-Befehle fuer den Interpreter.
zdiwdef.h wird von cpu.c benoetigt.

2. gcc -c -Wall -O3 zdiw.c

Das ist sozusagen die Bytecode/CPU-Bibliothek.

3. gcc -c -Wall -O3 zdiwex.c

Das ist ein Beispiel-Interpreter, der aber nur das Hallo-Welt-Programm ausfuehren kann! 

4. gcc -o zdiwex zdiwex.o zdiw.o

Linken... klar.

5. lua genzdiwhallo.lua

Enthaelt einen Assembler fuer Arme, der nur das Hallo-Welt-Programm assemblieren kann (auch dort enthalten).
Ausgabedatei ist zdiwhallo.bin.

6. zdiwex

Startet zdiwhallo.bin und sollte "Hallo Welt!" ausgeben.

So, jetzt kommen die Quelltexte geflogen. :-)
Ja tut mir leid, dass keine Einrueckungen drin sind...
PLATSCH!

-- Anfang genzdiw.lua

-- Aufruf: lua genzdiw.lua >zdiwdef.h

local def = [[

// Der unten folgende Generator beruecksichtigt nur die Listen mit dem Code in geschweiften Klammern.

_root:
* | 0 | 1
--+---+---
0 | _block0 _block1
1 | jmp jal

10 jmp j30 { nnpc = $j30 << 2; }
11 jal j30 { ra = pc + 8; nnpc = $j30 << 2; }

_block0:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | _group0 _group1 _group2 _group3
01 | beq bne ble bgt
10 | andi ori xori addspi 
11 | addi addoi _groupE _groupF

00 0100 beq r5 s5 b16 { if ($r5 == $s5) nnpc = pc + 4 + ($b16 << 2); }
00 0101 bne r5 s5 b16 { if ($r5 != $s5) nnpc = pc + 4 + ($b16 << 2); }
00 0110 ble r5 s5 b16 { if ((i32)$r5 <= (i32)$s5) nnpc = pc + 4 + ($b16 << 2); }
00 0111 bgt r5 s5 b16 { if ((i32)$r5 > (i32)$s5) nnpc = pc + 4 + ($b16 << 2); }
00 1000 andi >r5 s5 v16 { if (@r5) $r5 = $s5 & $v16; }
00 1001 ori >r5 s5 v16 { if (@r5) $r5 = $s5 | $v16; }
00 1010 xori >r5 s5 v16 { if (@r5) $r5 = $s5 ^ $v16; }
00 1011 addspi _10 n16 { sp += $n16; }
00 1100 addi >r5 s5 n16 { if (@r5) $r5 = $s5 + $n16; }
00 1101 addoi >r5 s5 n16 { unused($r5, $s5, $n16); err(E_UNK); }

_block1:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | slti sltiu bleu bgtu
01 | lwl lwr swl swr
10 | lb lh sw lw
11 | lbu lhu sb sh

01 0000 slti >r5 s5 n16 { if (@r5) $r5 = (i32)$s5 < $n16 ? 1 : 0; }
01 0001 sltiu >r5 s5 n16 { if (@r5) $r5 = $s5 < (u32)(i32)$n16 ? 1 : 0; }
01 0010 bleu r5 s5 b16 { if ($r5 <= $s5) nnpc = pc + 4 + ($b16 << 2); }
01 0011 bgtu r5 s5 b16 { if ($r5 > $s5) nnpc = pc + 4 + ($b16 << 2); }
01 0100 lwl >r5 s5 n16 { unused($r5, $s5, $n16); err(E_UNK); }
01 0101 lwr >r5 s5 n16 { unused($r5, $s5, $n16); err(E_UNK); }
01 0110 swl r5 s5 n16 { unused($r5, $s5, $n16); err(E_UNK); }
01 0111 swr r5 s5 n16 { unused($r5, $s5, $n16); err(E_UNK); }
01 1000 lb >r5 s5 n16 { if (@r5) load(i8, $s5 + $n16, $r5); }
01 1001 lh >r5 s5 n16 { if (@r5) load(i16, $s5 + $n16, $r5); }
01 1010 sw r5 s5 n16 { store(u32, $s5 + $n16, $r5); }
01 1011 lw >r5 s5 n16 { if (@r5) load(u32, $s5 + $n16, $r5); }
01 1100 lbu >r5 s5 n16 { if (@r5) load(u8, $s5 + $n16, $r5); }
01 1101 lhu >r5 s5 n16 { if (@r5) load(u16, $s5 + $n16, $r5); }
01 1110 sb r5 s5 n16 { store(u8, $s5 + $n16, $r5); }
01 1111 sh r5 s5 n16 { store(u16, $s5 + $n16, $r5); }

_group0:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | shl shr jmpv sar
01 | shlv shrv jalv sarv
10 | and or xor nor
11 | add addo sub subo

00 0000 0000 shl >r6 _2 s6 _3 v5 { if (@r6) $r6 = $s6 << $v5; }
00 0000 0001 shr >r6 _2 s6 _3 v5 { if (@r6) $r6 = $s6 >> $v5; }
00 0000 0010 jmpv _16 t6 { nnpc = $t6; }
00 0000 0011 sar >r6 _2 s6 _3 v5 { if (@r6) $r6 = (i32)$s6 >> $v5; }
00 0000 0100 shlv >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 << $t6; }
00 0000 0101 shrv >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 >> $t6; }
00 0000 0110 jalv >r6 _10 t6 { nnpc = $t6; if (@r6) $r6 = pc + 8; }
00 0000 0111 sarv >r6 _2 s6 _2 t6 { if (@r6) $r6 = (i32)$s6 >> $t6; }
00 0000 1000 and >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 & $t6; }
00 0000 1001 or >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 | $t6; }
00 0000 1010 xor >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 ^ $t6; }
00 0000 1011 nor >r6 _2 s6 _2 t6 { if (@r6) $r6 = ~($s6 | $t6); }
00 0000 1100 add >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 + $t6; }
00 0000 1101 addo >r6 _2 s6 _2 t6 { unused($r6, $s6, $t6); err(E_UNK); }
00 0000 1110 sub >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 - $t6; }
00 0000 1111 subo >r6 _2 s6 _2 t6 { unused($r6, $s6, $t6); err(E_UNK); }

_group1:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | slt sltu bltzal bgezal
01 | fpext eret sys brk
10 | inb inh outw inw
11 | inbu inhu outb outh

00 0001 0000 slt >r6 _2 s6 _2 t6 { if (@r6) $r6 = (i32)$s6 < (i32)$t6 ? 1 : 0; }
00 0001 0001 sltu >r6 _2 s6 _2 t6 { if (@r6) $r6 = $s6 < $t6 ? 1 : 0; }
00 0001 0010 bltzal r6 b16 { ra = pc + 8; if ((i32)$r6 < 0) nnpc = pc + 4 + ($b16 << 2); }
00 0001 0011 bgezal r6 b16 { ra = pc + 8; if ((i32)$r6 >= 0) nnpc = pc + 4 + ($b16 << 2); }
00 0001 0100 fpext x22 { fpext($x22); }
00 0001 0101 eret _22 { eret(); }
00 0001 0110 sys _22 { sys(); }
00 0001 0111 brk _22 { brk(); }
00 0001 1000 inb >r6 _2 s6 v8 { i32 t; periphinput(i8, $s6 + $v8, t); if (@r6) $r6 = t; }
00 0001 1001 inh >r6 _2 s6 v8 { i32 t; periphinput(i16, $s6 + $v8, t); if (@r6) $r6 = t; }
00 0001 1010 outw r6 _2 s6 v8 { periphoutput(u32, $s6 + $v8, $r6); }
00 0001 1011 inw >r6 _2 s6 v8 { u32 t; periphinput(u32, $s6 + $v8, t); if (@r6) $r6 = t; }
00 0001 1100 inbu >r6 _2 s6 v8 { u32 t; periphinput(u8, $s6 + $v8, t); if (@r6) $r6 = t; }
00 0001 1101 inhu >r6 _2 s6 v8 { u32 t; periphinput(u16, $s6 + $v8, t); if (@r6) $r6 = t; }
00 0001 1110 outb r6 _2 s6 v8 { periphoutput(u8, $s6 + $v8, $r6); }
00 0001 1111 outh r6 _2 s6 v8 { periphoutput(u16, $s6 + $v8, $r6); }

_group2:
* | 0 | 1
--+---+---
0 | mul mulu
1 | div divu

00 0010 00 mul >q6 >r6 s6 t6 { if (@q6 || @r6) { i64 t = (i64)$s6 * (i32)$t6; if (@q6) $q6 = t >> 32; if (@r6) $r6 = t; } }
00 0010 01 mulu >q6 >r6 s6 t6 { if (@q6 || @r6) { u64 t = (u64)$s6 * $t6; if (@q6) $q6 = t >> 32; if (@r6) $r6 = t; } }
00 0010 10 div >q6 >r6 s6 t6 { if (!$t6) err(E_DBZ); else { if (@q6) $q6 = (i32)$s6 % (i32)$t6; if (@r6) $r6 = (i32)$s6 / (i32)$t6; } }
00 0010 11 divu >q6 >r6 s6 t6 { if (!$t6) err(E_DBZ); else { if (@q6) $q6 = $s6 % $t6; if (@r6) $r6 = $s6 / $t6; } }

_group3:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | nop mv not neg
01 | mvi mvni cbw chw
10 | lui mvz svsp rssp
11 | mvo mvmo jmpra jalrav

00 0011 0000 nop _22 { }
00 0011 0001 mv >r6 _10 t6 { if (@r6) $r6 = $t6; }
00 0011 0010 not >r6 _10 t6 { if (@r6) $r6 = ~$t6; }
00 0011 0011 neg >r6 _10 t6 { if (@r6) $r6 = -$t6; }
00 0011 0100 mvi >r6 v16 { if (@r6) $r6 = $v16; }
00 0011 0101 mvni >r6 v16 { if (@r6) $r6 = -(i32)(u32)$v16; }
00 0011 0110 cbw >r6 _10 t6 { if (@r6) $r6 = (i8)$t6; }
00 0011 0111 chw >r6 _10 t6 { if (@r6) $r6 = (i16)$t6; }
00 0011 1000 lui >r6 v16 { if (@r6) $r6 = $v16 << 16; }
00 0011 1001 mvz >r6 _16 { if (@r6) $r6 = 0; }
00 0011 1010 svsp _22 { fp = sp; }
00 0011 1011 rssp _22 { sp = fp; }
00 0011 1100 mvo >r6 _16 { if (@r6) $r6 = 1; }
00 0011 1101 mvmo >r6 _16 { if (@r6) $r6 = -1; }
00 0011 1110 jmpra _22 { nnpc = ra; }
00 0011 1111 jalrav _16 t6 { nnpc = $t6; ra = pc + 8; }

_groupE:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | lbgp lhgp swgp lwgp
01 | lbgpu lhgpu sbgp shgp
10 | lbv lhv swv lwv
11 | lbuv lhuv sbv shv

00 1110 0000 lbgp >r6 n16 { if (@r6) load(i8, gp + $n16, $r6); }
00 1110 0001 lhgp >r6 n16 { if (@r6) load(i16, gp + $n16, $r6); }
00 1110 0010 swgp r6 n16 { store(u32, gp + $n16, $r6); }
00 1110 0011 lwgp >r6 n16 { if (@r6) load(u32, gp + $n16, $r6); }
00 1110 0100 lbgpu >r6 n16 { if (@r6) load(u8, gp + $n16, $r6); }
00 1110 0101 lhgpu >r6 n16 { if (@r6) load(u16, gp + $n16, $r6); }
00 1110 0110 sbgp r6 n16 { store(u8, gp + $n16, $r6); }
00 1110 0111 shgp r6 n16 { store(u16, gp + $n16, $r6); }
00 1110 1000 lbv >r6 _10 t6 { if (@r6) load(i8, $t6, $r6); }
00 1110 1001 lhv >r6 _10 t6 { if (@r6) load(i16, $t6, $r6); }
00 1110 1010 swv r6 _10 t6 { store(u32, $t6, $r6); }
00 1110 1011 lwv >r6 _10 t6 { if (@r6) load(u32, $t6, $r6); }
00 1110 1100 lbuv >r6 _10 t6 { if (@r6) load(u8, $t6, $r6); }
00 1110 1101 lhuv >r6 _10 t6 { if (@r6) load(u16, $t6, $r6); }
00 1110 1110 sbv r6 _10 t6 { store(u8, $t6, $r6); }
00 1110 1111 shv r6 _10 t6 { store(u16, $t6, $r6); }

_groupF:
** | 00 | 01 | 10 | 11
---+----+----+----+----
00 | lbsp lhsp swsp lwsp
01 | lbspu lhspu sbsp shsp
10 | lbfp lhfp swfp lwfp
11 | lbfpu lhfpu sbfp shfp

00 1111 0000 lbsp >r6 n16 { if (@r6) load(i8, sp + $n16, $r6); }
00 1111 0001 lhsp >r6 n16 { if (@r6) load(i16, sp + $n16, $r6); }
00 1111 0010 swsp r6 n16 { store(u32, sp + $n16, $r6); }
00 1111 0011 lwsp >r6 n16 { if (@r6) load(u32, sp + $n16, $r6); }
00 1111 0100 lbspu >r6 n16 { if (@r6) load(u8, sp + $n16, $r6); }
00 1111 0101 lhspu >r6 n16 { if (@r6) load(u16, sp + $n16, $r6); }
00 1111 0110 sbsp r6 n16 { store(u8, sp + $n16, $r6); }
00 1111 0111 shsp r6 n16 { store(u16, sp + $n16, $r6); }
00 1111 1000 lbfp >r6 n16 { if (@r6) load(i8, fp + $n16, $r6); }
00 1111 1001 lhfp >r6 n16 { if (@r6) load(i16, fp + $n16, $r6); }
00 1111 1010 swfp r6 n16 { store(u32, fp + $n16, $r6); }
00 1111 1011 lwfp >r6 n16 { if (@r6) load(u32, fp + $n16, $r6); }
00 1111 1100 lbfpu >r6 n16 { if (@r6) load(u8, fp + $n16, $r6); }
00 1111 1101 lhfpu >r6 n16 { if (@r6) load(u16, fp + $n16, $r6); }
00 1111 1110 sbfp r6 n16 { store(u8, fp + $n16, $r6); }
00 1111 1111 shfp r6 n16 { store(u16, fp + $n16, $r6); }

]]

local errline = ""
local lno = 0
local function sprintf(fmt, ...) return "" .. string.format(fmt, ...) end
local function fprintf(f, fmt, ...) f:write(sprintf(fmt, ...)) end
local function printf(fmt, ...) fprintf(io.stdout, fmt, ...) end
local function err(fmt, ...) fprintf(io.stderr, "%u: %s\n", lno, errline); fprintf(io.stderr, fmt .. "\n", ...); os.exit(1) end

local list = {}
string.gsub(def, "([^\n]*)\n",
function(line)
lno = lno + 1
errline = line
line = string.gsub(line, "%/%/[^\n]*$", "")
local f, t, opc, mnm, stx, code = string.find(line, "^([01%s]+)(%a+)([^%{]+)(%{[^\n]+)$", 1)
if not f then
if string.find(line, "^%s*$", 1) then return end
if string.find(line, "^_%w*%s*%:%s*$", 1) then return end
if string.find(line, "^%*+[01%|%s]+$", 1) then return end
if string.find(line, "^%-+[%+%-]+%s*$", 1) then return end
if string.find(line, "^[01]+%s+%|[%w_%s]+$", 1) then return end
err("line format")
return
end -- if
opc = string.gsub(opc, "%s+", "")
local width = #opc
opc = tonumber(opc, 2)
local lastopc = opc
if width < 10 then
opc = opc * (2 ^ (10 - width))
lastopc = opc + 2 ^ (10 - width) - 1
end -- if
width = 32 - width
local arg = {}
string.gsub(stx, "([%>]?)([_bjnqrstvx])(%d+)",
function(out, k, w)
local id = k .. w
w = tonumber(w)
width = width - w
arg[id] = { k = k, o = out == ">", l = width, a = 2 ^ w - 1 }
end)
if width ~= 0 then err("width: " .. width) end
code = string.gsub(code, "%$(%w+)", 
function(id)
if arg[id].o then return "" .. string.format("ureg[a%s]", id) end
return id
end)
code = string.gsub(code, "%@", "a")
local out = ""
for i = opc, lastopc do
if i == lastopc then out = out .. sprintf("case 0x%03x : { // %s%s\n", i, mnm, stx)
else out = out .. sprintf("case 0x%03x :\n", i)
end -- if
end -- for i
local k, o, l, a
for id, arg in pairs(arg) do
k, o, l, a = arg.k, arg.o, arg.l, arg.a
if string.find(k, "^[bn]", 1) then out = out .. sprintf("i32 %s = (i16)opc;\n", id)
elseif string.find(k, "^[jvx]", 1) then out = out .. sprintf("u32 %s = opc & 0x%x;\n", id, a)
elseif string.find(k, "^[qrst]", 1) then
if o then out = out .. sprintf("u32 a%s = (opc >> %u) & 0x%x;\n", id, l, a)
else out = out .. sprintf("u32 %s = ureg[(opc >> %u) & 0x%x];\n", id, l, a)
end -- if
elseif k ~= "_" then err("invalid arg")
end -- if
end -- for id, arg
out = out .. sprintf("%s\n", code)
out = out .. sprintf("} break;\n")
table.insert(list, out)
end)
table.sort(list)
for i = 1, #list do printf("%s", list[i]) end

-- Ende genzdiw.lua

PLATSCH! :-)

/*** Anfang zdiw.h ***/

#ifndef ZDIW_H
#define ZDIW_H

#include 

typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

typedef struct {
u32 ureg[64];
u8 *mem;
u32 msz;
u32 pc;
u32 npc;
u32 nnpc;
} cpu_t;

enum _err_ {
E_OK = 0,
E_DBZ,
E_UNK,
E_NXT,
E_LDM,
E_STM,
E_LDP,
E_STP,
E_FPX,
E_ERT,
E_SYS,
E_BRK,
E_USR = 0x80000000,
E_ARG = 0xffffffff
};

// von Applikation zu implementieren
u32 i8input(u32 a, i32 *v);
u32 i16input(u32 a, i32 *v);
u32 u8input(u32 a, u32 *v);
u32 u16input(u32 a, u32 *v);
u32 u32input(u32 a, u32 *v);
u32 u8output(u32 a, u32 v);
u32 u16output(u32 a, u32 v);
u32 u32output(u32 a, u32 v);

// zdiw-Funktionen
cpu_t *cpu_create(u32 msz);
u32 cpu_destroy(cpu_t *cpu);
u32 cpu_run(cpu_t *cpu);

#endif

/*** Ende zdiw.h ***/

PLATSCH! ... nervt's? :-)

/*** Anfang zdiw.c ***/

#include 

#include "zdiw.h"

#define gp ureg[28]
#define sp ureg[29]
#define fp ureg[30]
#define ra ureg[31]

#define err(_q) do { q = _q; goto exitloop; } while (0)

#define _load(_t, _a, _v, _e) \
do { \
u32 a = _a; \
u32 a1 = a + sizeof(_t); \
if (a >= msz || a1 > msz || a1 < a) err(_e); \
else _v = *(_t *)(mem + a); \
} while (0)

#define _store(_t, _a, _v, _e) \
do { \
u32 a = _a; \
u32 a1 = a + sizeof(_t); \
if (a >= msz || a1 > msz || a1 < a) err(_e); \
else *(_t *)(mem + a) = _v; \
} while (0)

#define next(_nt, _na, _nv) _load(_nt, _na, _nv, E_NXT)
#define load(_lt, _la, _lv) _load(_lt, _la, _lv, E_LDM)
#define store(_st, _sa, _sv) _store(_st, _sa, _sv, E_STM)

#define periphinput(_pit, _pia, _piv) \
do { \
cpu->pc = pc; cpu->npc = npc; cpu->nnpc = nnpc; \
q = _pit##input(_pia, &_piv); \
pc = cpu->pc; npc = cpu->npc; nnpc = cpu->nnpc; \
if (q) err(q); \
} while (0)

#define periphoutput(_pot, _poa, _pov) \
do { \
cpu->pc = pc; cpu->npc = npc; cpu->nnpc = nnpc; \
q = _pot##output(_poa, _pov); \
pc = cpu->pc; npc = cpu->npc; nnpc = cpu->nnpc; \
if (q) err(q); \
} while (0)

#define fpext(subopc) do { (void)subopc; err(E_FPX); } while (0)
#define eret() do { err(E_ERT); } while (0)
#define sys() do { err(E_SYS); } while (0)
#define brk() do { err(E_BRK); } while (0)

#define unused(_a, _b, _c) do { (void)_a; (void)_b; (void)_c; } while (0)

cpu_t *cpu_create(u32 msz)
{
cpu_t *cpu;
u8 *mem;
if (msz < 1024 || (msz & 3)) return NULL;
cpu = malloc(sizeof(*cpu));
if (!cpu) return NULL;
mem = malloc(msz);
if (!mem) { free(cpu); return NULL; }
cpu->mem = mem;
cpu->msz = msz;
*(u32 *)mem = 0;
cpu->ureg[0] = 0;
return cpu;
}

u32 cpu_destroy(cpu_t *cpu)
{
if (!cpu) return E_ARG;
if (!cpu->mem) return E_ARG;
if (cpu->msz < 1024 || (cpu->msz & 3)) return E_ARG;
free(cpu->mem);
cpu->mem = NULL;
cpu->msz = 0;
free(cpu);
return E_OK;
}

u32 cpu_run(cpu_t *cpu)
{
u8 *mem;
u32 *ureg;
u32 msz, q, pc, npc, nnpc;
if (!cpu) return E_ARG;
mem = cpu->mem;
if (!mem) return E_ARG;
msz = cpu->msz;
if (msz < 1024 || (msz & 3)) return E_ARG;
ureg = cpu->ureg;
pc = cpu->pc;
npc = cpu->npc;
nnpc = cpu->nnpc;
ureg[0] = q = 0;
for (;;)
{
u32 opc;
pc = npc; npc = nnpc; nnpc += 4;
next(u32, pc, opc);
switch (opc >> 22)
{
default : err(E_UNK); break;
#include "zdiwdef.h"
} // switch
} // for
exitloop:
cpu->pc = pc;
cpu->npc = npc;
cpu->nnpc = nnpc;
return q;
}

/*** Ende zdiw.c ***/

PLATSCH! :-)

/*** Anfang von zdiwex.c ***/

#include 
#include 

#include "zdiw.h"

u32 u32output(u32 a, u32 v)
{
switch (a)
{
case 0x00 :
return E_USR | E_OK;
case 0x10 :
putchar((char)v);
return E_OK;
} // switch
return E_STP;
}

u32 u16output(u32 a, u32 v) { return u32output(a, v); }
u32 u8output(u32 a, u32 v) { return u32output(a, v); }

u32 u32input(u32 a, u32 *v)
{
(void)a;
(void)v;
return E_LDP;
}

u32 u16input(u32 a, u32 *v) { return u32input(a, v); }
u32 u8input(u32 a, u32 *v) { return u32input(a, v); }
u32 i16input(u32 a, i32 *v) { u32 t; return u32input(a, &t); *v = t; }
u32 i8input(u32 a, i32 *v) { u32 t; return u32input(a, &t); *v = t; }

int main(void)
{
u32 loadaddr, q;
FILE *src;
cpu_t *cpu;
cpu = cpu_create(0x10000); // 64KB memory
if (!cpu) 
{
cpu_destroy(cpu);
return -1;
} // if
src = fopen("zdiwhallo.bin", "rb");
if (!src) return -2;
fread(&loadaddr, sizeof(loadaddr), 1, src);
fread(cpu->mem + loadaddr, 1, 0x10000, src);
fclose(src);
cpu->pc = 0;
cpu->npc = loadaddr;
cpu->nnpc = loadaddr + 4;
for (;;)
{
q = cpu_run(cpu);
if (q == (E_USR | E_OK)) break;
printf("run time error: %u\n", q);
return -3;
} // for
cpu_destroy(cpu);
return 0;
}

/*** Ende von zdiwex.c ***/

... und letztes PLATSCH! :-)

-- Anfang genzdiwhallo.lua

-- Assembler fuer Arme :-)

local zero, at, v0, v1, a0, a1, a2, a3 = 0, 1, 2, 3, 4, 5, 6, 7
local t0, t1, t2, t3, t4, t5, t6, t7 = 8, 9, 10, 11, 12, 13, 14, 15
local s0, s1, s2, s3, s4, s5, s6, s7 = 16, 17, 18, 19, 20, 21, 22, 23
local t8, t9, k0, k1, gp, sp, fp, ra = 24, 25, 26, 27, 28, 29, 30, 31
local c0, c1, c2, c3, c4, c5, c6, c7 = 32, 33, 34, 35, 36, 37, 38, 39
local y0, y1, y2, y3, y4, y5, y6, y7 = 40, 41, 42, 43, 44, 45, 46, 47
local x0, x1, x2, x3, x4, x5, x6, x7 = 48, 49, 50, 51, 52, 53, 54, 55
local lo, hi, inf0, inf1, inf2, inf3, ira0, ira1 = 56, 57, 58, 59, 60, 61, 62, 63

local pass = 0
local mem = {}
local orgloc, loc = 0, 0
local lab = {}

local function org(a) 
orgloc, loc = a, a
end

local function _(s) 
if pass == 1 then lab[s] = loc end
end

local function byte(...)
local arg = {...}
local a
for i = 1, #arg do
a = arg[i]
if type(a) == "string" then
if #a > 0 then for j = 1, #a do byte(string.byte(a, j)) end end
else
if pass == 2 then 
if not a then error("nil-poke") end
mem[loc] = a 
end
loc = loc + 1
end -- if
end -- for i
end

local function align()
while loc % 4 ~= 0 do byte(0) end
end

local function word(a)
local b = a % 0x100
byte(b)
a = (a - b) / 0x100
b = a % 0x100
byte(b)
a = (a - b) / 0x100
b = a % 0x100
byte(b)
b = (a - b) / 0x100
byte(b)
end

local function high(a)
if pass == 1 then return 0 end
a = lab[a]
a = a - a % 0x10000
return a / 0x10000
end

local function low(a)
if pass == 1 then return 0 end
a = lab[a]
return a % 0x10000
end

local function nop()
word(0x0c0000)
end

local function lui(a, b)
word(0x0e0000 + a * 0x10000 + b)
end

local function ori(a, b, c)
word(0x24000000 + a * 0x10000 * 0x20 + b * 0x10000 + c)
end

local function addi(a, b, c)
word(0x30000000 + a * 0x10000 * 0x20 + b * 0x10000 + c)
end

local function lbu(a, b, c)
word(0x70000000 + a * 0x10000 * 0x20 + b * 0x10000 + c)
end

local function outb(a, b, c)
word(0x07800000 + a * 0x100 * 0x100 + b * 0x100 + c)
end

local function jmp(a)
if pass == 1 then a = 0
else a = lab[a]
end -- if
word(0x80000000 + a / 4)
end

local function beq(a, b, c)
if pass == 1 then c = 0
else c = (lab[c] - loc - 4) / 4
end -- if
if c < 0 then c = c + 0x10000 end
word(0x10000000 + a * 0x10000 * 0x20 + b * 0x10000 + c)
end

local function beginasm(p)
pass = p
end

local function endasm()
if pass == 1 then return "" end
local s = ""
for i = orgloc, loc - 1 do
s = s .. string.char(mem[i])
end -- for i
return s
end

-- Beispielprogramm

local bin
for pass = 1, 2 do
beginasm(pass)
org(0x4000)
lui(t0, high("_text"))
ori(t0, t0, low("_text"))
_ "L1"
lbu(t1, t0, 0)
beq(t1, zero, "L2")
addi(t0, t0, 1)
jmp("L1")
outb(t1, zero, 0x10) -- Konsolenausgabe
_ "L2"
outb(zero, zero, 0) -- Stop
_ "L3"
jmp("L3") -- Halt
nop()
_ "_text"
byte("\nHallo Welt!\n\0")
align()
bin = endasm()
end -- for pass

local dst = io.open("zdiwhallo.bin", "wb")
dst:write(string.char(orgloc % 0x100, (orgloc - orgloc % 0x100) / 0x100), "\0\0") -- Startaddresse
dst:write(bin)
dst:close()

-- Ende genzdiwhallo.lua

... koennen natuerlich immer noch Fehler drin sein - ziemlich wahrscheinlich sogar.
...und die Fliesskommaarithmetik fehlt sowieso noch.
...der JIT-Compiler auch noch.
... der gcc-Code-Konverter auch noch.
...und definierte Geraete (Ports) auch noch.
...und ein Betriebssystem auch noch.
...und eine MMU auch noch.
... und eine WebAssembly-Portierung auch noch.
... und natuerlich Anwendungen, die laufen...
... und noch dieses und jenes.
Ach je...  so alt werde ich gar nicht... :-/

############################################################
==========

Mal etwas anderes und nur so eine Idee...

Meine MIPS-teilkompatible Software-CPU/mein Bytecode-Interpreter. :-)

Einen Namen braeuchte es noch... hm...
MIPS auf den Kopf gestellt ergibt WIbS, horizontal gespiegelt und mit etwas Phantasie ZdIW.
Na gut, der Name ist eigentlich ziemlich kacke, den nehm ich. :-)

Mit einem zusaetzlichen Code-Konverter kann man einen MIPS-gcc verwenden um fuer ZdIW Programme zu schreiben.
Dazu muessen allerdings einige Sachen ueber Kommandozeilenparameter eingestellt werden.
Mehr Erklaerung gibt's nicht. Also selbst Dokus zu MIPS/gcc-Optionen/gcc's soft-float waelzen... wobei nachfolgend aber einiges anders ist als bei der MIPS.

Es soll halt moeglichst einfach gehalten sein, der gcc soll verwendet werden koennen und es soll moeglichst schnell als Soft-CPU auch ohne JIT-Compiler laufen.
Die Out-of-Order-Ausfuehrung waere ich gerne auch noch los geworden; gcc-Ausgaben gehen dann aber nicht zuverlaessig in ZdIW-Code zu konvertieren.

Ach ja, als Vorlage verwendete ich den "Grundbefehlssatz" einer 32-Bit-MIPS und meine Experimente habe ich alle im Little-Endian-Format durchgefuehrt. Letzteres sollte weitgehend egal sein, solange diese nervigen lwl/lwr/swl/swr-Befehle nicht verwendet werden.

Geht los... 
... PLATSCH! :-) ...

Opcode-Felder der Instruktionen

_2 etc. = unbenutzte n Bits
b16 (vorzeichenbehaftet) = relatives Verzweigungsziel, 16 Bits
j30 (vorzeichenlos) = absolutes Sprungziel, 30 Bits
n16 etc. (vorzeichenbehaftet) = Konstante, hier 16 Bits
v16 etc. (vorzeichenlos) = Konstante, hier 16 Bits
q6/p6/r6/s6 etc. (vorzeichenlos) = Register (ein Groesserzeichen kennzeichnet Zielregister), hier bspw. 6 Bits
x22 etc. (vorzeichenlos) = Sub-Opcode (Floating-Point-Erweiterung), hier 22 Bits

Instruktionen:

// Register-Instruktionen
00 0000 0000 shl >r6 _2 s6 _3 v5 { $r6 = $s6 << $v5; }
00 0000 0001 shr >r6 _2 s6 _3 v5 { $r6 = $s6 >> $v5; }
00 0000 0010 jmpv _16 t6 { nnpc = $t6; }
00 0000 0011 sar >r6 _2 s6 _3 v5 { $r6 = (i32)$s6 >> $v5; }
00 0000 0100 shlv >r6 _2 s6 _2 t6 { $r6 = $s6 << $t6; }
00 0000 0101 shrv >r6 _2 s6 _2 t6 { $r6 = $s6 >> $t6; }
00 0000 0110 jalv >r6 _10 t6 { $r6 = pc + 8; nnpc = $t6; }
00 0000 0111 sarv >r6 _2 s6 _2 t6 { $r6 = (i32)$s6 >> $t6; }
00 0000 1000 and >r6 _2 s6 _2 t6 { $r6 = $s6 & $t6; }
00 0000 1001 or >r6 _2 s6 _2 t6 { $r6 = $s6 | $t6; }
00 0000 1010 xor >r6 _2 s6 _2 t6 { $r6 = $s6 ^ $t6; }
00 0000 1011 nor >r6 _2 s6 _2 t6 { $r6 = ~($s6 | $t6); }
00 0000 1100 add >r6 _2 s6 _2 t6 { $r6 = $s6 + $t6; }
00 0000 1101 // reserviert fuer addo
00 0000 1110 sub >r6 _2 s6 _2 t6 { $r6 = $s6 - $t6; }
00 0000 1111 // reserviert fuer subo
00 0001 0000 slt >r6 _2 s6 _2 t6 { $r6 = (i32)$s6 < (i32)$t6 ? 1 : 0; }
00 0001 0001 sltu >r6 _2 s6 _2 t6 { $r6 = $s6 < $t6 ? 1 : 0; }
00 0001 0010 fpext x22 { floatingpointextension(x22); }
00 0001 0011 eret _22 { eret(); }
00 0001 0100 sys _22 { sys(); }
00 0001 0101 brk _22 { brk(); }
// Kompatibilitaet
00 0001 0110 bltzal r6 b16 { ra = pc + 8; if ((i32)$r6 < 0) nnpc = pc + 4 + ($b16 << 2); }
00 0001 0111 bgezal r6 b16 { ra = pc + 8; if ((i32)$r6 >= 0) nnpc = pc + 4 + ($b16 << 2); }
// zur Vereinfachung/Beschleunigung der Speicherzugriffe, hier seperate I/O-Befehle
00 0001 1000 inb >r6 _2 s6 v8 { periphinput(i8, $s6 + $v8, $r6); }
00 0001 1001 inh >r6 _2 s6 v8 { periphinput(i16, $s6 + $v8, $r6); }
00 0001 1010 outw r6 _2 s6 v8 { periphoutput(u32, $s6 + $v8, $r6); }
00 0001 1011 inw >r6 _2 s6 v8 { periphinput(u32, $s6 + $v8, $r6); }
00 0001 1100 inbu >r6 _2 s6 v8 { periphinput(u8, $s6 + $v8, $r6); }
00 0001 1101 inhu >r6 _2 s6 v8 { periphinput(u16, $s6 + $v8, $r6); }
00 0001 1110 outb r6 _2 s6 v8 { periphoutput(u8, $s6 + $v8, $r6); }
00 0001 1111 outh r6 _2 s6 v8 { periphoutput(u16, $s6 + $v8, $r6); }
// Multiplikation/Division mit Zielregisterangabe erspart mfhi/mthi usw.
0000 1000 mul >q6 >r6 s6 t6 { i64 t = (i64)$s6 * (i32)$t6; $q6 = t >> 32; $r6 = t; }
000 1001 mulu >q6 >r6 s6 t6 { u64 t = (u64)$s6 * (u32)$t6; $q6 = t >> 32; $r6 = t; }
0000 1010 div >q6 >r6 s6 t6 { if (!$t6) err(E_DBZ); else { $q6 = (i32)$s6 % (i32)$t6; $r6 = (i32)$s6 / (i32)$t6; } }
0000 1011 divu >q6 >r6 s6 t6 { if (!$t6) err(E_DBZ); else { $q6 = $s6 % $t6; $r6 = $s6 / $t6; } }
// Software-Interpreter-Beschleunigung
00 0011 0000 nop _22 { }
00 0011 0001 mv >r6 _10 t6 { $r6 = $t6; }
00 0011 0010 not >r6 _10 t6 { $r6 = ~$t6; }
00 0011 0011 neg >r6 _10 t6 { $r6 = -$t6; }
00 0011 0100 mvi >r6 v16 { $r6 = $v16; }
00 0011 0101 mvni >r6 v16 { $r6 = -$v16; }
00 0011 0110 cbw >r6 _10 t6 { $r6 = (i32)(i8)$t6; }
00 0011 0111 chw >r6 _10 t6 { $r6 = (i32)(i16)$t6; }
00 0011 1000 mvz >r6 _16 { $r6 = 0; }
00 0011 1001 lui >r6 v16 { $r6 = $v16 << 16; }
00 0011 1010 mvo >r6 _16 { $r6 = 1; }
00 0011 1011 mvmo >r6 _16 { $r6 = -1; }
00 0011 1100 svsp _22 { fp = sp; }
00 0011 1101 rssp _22 { sp = fp; }
00 0011 1110 jmpvra _22 { nnpc = ra; }
00 0011 1111 jalvra _16 t6 { ra = pc + 8; nnpc = $t6; }
// wieder bedingte Spruenge
00 0100 beq r5 s5 b16 { if ($r5 == $s5) nnpc = pc + 4 + ($b16 << 2); }
00 0101 bne r5 s5 b16 { if ($r5 != $s5) nnpc = pc + 4 + ($b16 << 2); }
00 0110 ble r5 s5 b16 { if ((i32)$r5 <= (i32)$s5) nnpc = pc + 4 + ($b16 << 2); }
00 0111 bgt r5 s5 b16 { if ((i32)$r5 > (i32)$s5) nnpc = pc + 4 + ($b16 << 2); }
// Immediate-Befehle
00 1000 andi >r5 s5 v16 { $r5 = $s5 & $v16; }
00 1001 ori >r5 s5 v16 { $r5 = $s5 | $v16; }
00 1010 xori >r5 s5 v16 { $r5 = $s5 ^ $v16; }
00 1011 addspi _5 n16 { sp += $n16; }
00 1100 addi >r5 s5 n16 { $r5 = $s5 + $n16; }
00 1101 // reserviert fuer addoi
// beschleunigte Speicherzugriffe relativ zu gp/sp/fp oder ohne Offset
00 1110 0000 lbgp >r6 n16 { load(i8, gp + $n16, $r6); }
00 1110 0001 lhgp >r6 n16 { load(i16, gp + $n16, $r6); }
00 1110 0010 swgp r6 n16 { store(u32, gp + $n16, $r6); }
00 1110 0011 lwgp >r6 n16 { load(u32, gp + $n16, $r6); }
00 1110 0100 lbgpu >r6 n16 { load(u8, gp + $n16, $r6); }
00 1110 0101 lhgpu >r6 n16 { load(u16, gp + $n16, $r6); }
00 1110 0110 sbgp r6 n16 { store(u8, gp + $n16, $r6); }
00 1110 0111 shgp r6 n16 { store(u16, gp + $n16, $r6); }
00 1110 1000 lbv >r6 _10 t6 { load(i8, $t6, $r6); }
00 1110 1001 lhv >r6 _10 t6 { load(i16, $t6, $r6); }
00 1110 1010 swv r6 _10 t6 { store(u32, $t6, $r6); }
00 1110 1011 lwv >r6 _10 t6 { load(u32, $t6, $r6); }
00 1110 1100 lbuv >r6 _10 t6 { load(u8, $t6, $r6); }
00 1110 1101 lhuv >r6 _10 t6 { load(u16, $t6, $r6); }
00 1110 1110 sbv r6 _10 t6 { store(u8, $t6, $r6); }
00 1110 1111 shv r6 _10 t6 { store(u16, $t6, $r6); }
00 1111 0000 lbsp >r6 n16 { load(i8, sp + $n16, $r6); }
00 1111 0001 lhsp >r6 n16 { load(i16, sp + $n16, $r6); }
00 1111 0010 swsp r6 n16 { store(u32, sp + $n16, $r6); }
00 1111 0011 lwsp >r6 n16 { load(u32, sp + $n16, $r6); }
00 1111 0100 lbspu >r6 n16 { load(u8, sp + $n16, $r6); }
00 1111 0101 lhspu >r6 n16 { load(u16, sp + $n16, $r6); }
00 1111 0110 sbsp r6 n16 { store(u8, sp + $n16, $r6); }
00 1111 0111 shsp r6 n16 { store(u16, sp + $n16, $r6); }
00 1111 1000 lbfp >r6 n16 { load(i8, fp + $n16, $r6); }
00 1111 1001 lhfp >r6 n16 { load(i16, fp + $n16, $r6); }
00 1111 1010 swfp r6 n16 { store(u32, fp + $n16, $r6); }
00 1111 1011 lwfp >r6 n16 { load(u32, fp + $n16, $r6); }
00 1111 1100 lbfpu >r6 n16 { load(u8, fp + $n16, $r6); }
00 1111 1101 lhfpu >r6 n16 { load(u16, fp + $n16, $r6); }
00 1111 1110 sbfp r6 n16 { store(u8, fp + $n16, $r6); }
00 1111 1111 shfp r6 n16 { store(u16, fp + $n16, $r6); }
// weiter mit Immediate-Befehlen
01 0000 slti >r5 s5 n16 { $r5 = (i32)$s5 < $n16; }
01 0001 sltiu >r5 s5 n16 { $r5 = $s5 < (u32)(i32)$n16; }
// nochmal bedingte Spruenge
01 0010 bleu r5 s5 b16 { if ($r5 <= $s5) nnpc = pc + 4 + ($b16 << 2); }
01 0011 bgtu r5 s5 b16 { if ($r5 > $s5) nnpc = pc + 4 + ($b16 << 2); }
// ich hasse diese Dinger (sind so kompliziert und werden in ZdIW nicht benoetigt)
01 0100 // reserviert fuer lwl
01 0101 // reserviert fuer lwr
01 0110 // reserviert fuer swl
01 0111 // reserviert fuer swr
// regulaere Speicherzugriffe
01 1000 lb >r5 s5 n16 { load(i8, $s5 + $n16, $r5); }
01 1001 lh >r5 s5 n16 { load(i16, $s5 + $n16, $r5); }
01 1010 sw r5 s5 n16 { store(u32, $s5 + $n16, $r5); }
01 1011 lw >r5 s5 n16 { load(u32, $s5 + $n16, $r5); }
01 1100 lbu >r5 s5 n16 { load(u8, $s5 + $n16, $r5); }
01 1101 lhu >r5 s5 n16 { load(u16, $s5 + $n16, $r5); }
01 1110 sb r5 s5 n16 { store(u8, $s5 + $n16, $r5); }
01 1111 sh r5 s5 n16 { store(u16, $s5 + $n16, $r5); }
// unbedingte 4GB-Spruenge
10 jmp j30 { nnpc = $j30 << 2; }
11 jal j30 { ra = pc + 8; nnpc = $j30 << 2; }

Anmerkungen:

* Der Code in geschweiften Klammern ist so nicht ganz funktionstuechtig.
Zum Beispiel ist klar, dass das $zero-Register niemals auf einen anderen Wert gesetzt werden kann.
* fpext noch nicht spezifiziert.
Die Fliesskomma-Befehle werden funktionieren wie die GNU-Soft-Float-Library-Funktionen.
Es fehlt nur die Durchnumerierung.
* eret/sys/brk noch nicht spezifiziert
* err (in div/divu) noch nicht spezifiziert
* Die Speicherbefehle verlangen keine Typausrichtung der Adressen.
* Die als reserviert gekennzeichneten Befehle sollten eigentlich niemals implementiert werden muessen.
Ich frage mich, wofuer man bltzal/bgezal braucht, wenn (laut MIPS-Doku) die Ruecksprungadresse in JEDEM FALL vor dem Sprung oder Nichtsprung in $ra gespeichert wird.
Was macht man mit so einem Ding? Eigentlich kann man das auch auskommentieren... ich weiss es nicht.

Ausfuehrungsschleife (hier mit switch):

// Die maximale Breite des eigentlichen Befehls-Codes betraegt 10 Bit (ohne Beruecksichtigung der FP-Arithmetik).
// Eine Sprungtabelle haette demnach 1024 Eintraege => 4096 Byte auf 32-Bit-Systemen.
// Bei kuerzeren Befehlscodes als 10 Bit ragen die Operanden zwar noch in den Opcode (Befehlsnummer) rein. Das macht aber nichts - die Sprungtabelle funktioniert natuerlich trotzdem.

for (;;)
{
  u32 ins;
  pc = npc; npc = nnpc; nnpc += 4;
  load(u32, pc, ins);
  switch (ins >> 22)
  {
  ...
  }
}

Register:

0-7 $zero, $at, $v0, $v1, $a0, $a1, $a2, $a3 // Register 0 bis 31 wie MIPS
8-15 $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7
16-23 $s0, $s1, $s2, $s3, $s4, $s5, $s6, $s7
24-31 $t8, $t9, $k0, $k1, $gp, $sp, $fp, $ra
32-39 $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7 // frei/Benutzerkonstanten (ueber laengere Zeit unveraendert)
40-47 $y0, $y1, $y2, $y3, $y4, $y5, $y6, $y7 // frei/mehr ungesicherte Register
48-55 $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7 // frei/mehr gesicherte Register
56-57 $lo, $hi // fuer MIPS-kompatibles mul/mulu/div/divu
58-61 $inf0, $inf1, $inf2, $inf3 // noch nicht spezifiziert (evtl. Unterbrechungen)
62-63 $ira0, $ira1 // noch nicht spezifiziert; fuer Unterbrechungen

(April 2017)

==========

*** ACHTUNG! NEU 2015! ***


mb2psp
======

... boah, bin voellig uebermuedet, wie immer... nur so nebenbei. :-)

Konvertiert die phonetische Ausgabe von eSpeak in das phonetische Format fuer PSpSyn (siehe unten).
eSpeak findet man hier:
http://espeak.sourceforge.net/
Benoetigt wird ausserdem noch eine Voice-Datei von hier (ich habe nur de2 getestet):
http://www.tcts.fpms.ac.be/synthesis/mbrola/mbrcopybin.html
Die Voice-Datei de2 wird fuer eSpeak nur zur Transkription benoetigt, nicht zur Audio-Erzeugung und solange man auch keine Sprache damit hoerbar machen will, wird der Mbrola-Synthesizer auch nicht benoetigt (sonst den auch noch herunterladen, ist hier aber nicht das Thema).
Wo diese de2-Datei hinkopiert werden muss bitte in der eSpeak-Doku nachlesen.

Verwendung von mb2psp.lua:
espeak -q -v mb-de2 --pho --phonout=esp.txt -f meintext.txt
lua mb2psp.lua <esp.txt >psp.txt

Die psp.txt kann nun von PSpSyn vorgelesen werden.
Ich habe in das PSpSyn-Archiv (pspsyn2015.zip, siehe unten) noch ein kleines Programm namens psay.lua rein kopiert, das Text von der Standardeingabe liest und in out.mp3 das Ergebnis schreibt. Obige psp.txt wird dann also wie folgt in out.mp3 gewandelt:

lua psay.lua <psp.txt

Wohlgemerkt: out.mp3 wird hier nicht mehr angegeben! Dieser Name ist in psay.lua fest vorgegeben (laesst sich ja leicht aendern).
out.mp3 kann jedenfalls dann mit einem entsprechenden Abspielprogramm wiedergegeben werden.

Es ist nicht so klar verstaendlich wie eSpeak selbst, aber ich konnte einen mir nicht vorher bekannten Text schon verstehen.

Dieses Programm ist nuetzlich solange es noch keine eigene Graphemtext-Phonemtext-Transkription gibt (siehe im Abschnitt ueber den Praeprozessor unten).
Oben habe ich auf meinen Praeprozessor verzichtet, weil der im Archiv kein Programm enthaelt, das oben direkt verwendet werden koennte. Ein solches zu tippen ist schnell erledigt und dann saehe der ganze Kommando-Salat eben so aus (angenommen das Praeprozessor-Programm hiesse pp.lua und ffmpeg unten ist der mp3-Player):

lua pp.lua <meintext.txt >tmp.txt
espeak -q -v mb-de2 --pho --phonout=esp.txt -f tmp.txt
lua mb2psp.lua <esp.txt >psp.txt
lua psay.lua <psp.txt
ffmpeg out.mp3

Der Praeprozessordurchlauf duerfte aber kaum merklich sein, da eSpeak selbst einen beinhaltet.
Wie auch immer - hier also der Link zum Download von mb2psp.zip:
http://www.erbsenkopf.de/mb2psp.zip

Wie immer... von mir aus zur freiesten Verfuegung.


Ein schmuddeliger kleiner TTS-Praeprozessor fuer PSpSyn
=======================================================

... geschrieben in weniger als 350 Zeilen Lua-Code. :-)

Das ist die erste Stufe (nach dem Einlesen eines Textes an sich) eines TTS-Systems: der Praeprozessor.
Eine phonetische Sprachausgabe findet sich weiter unten (letzte Stufe).
Es fehlen aber noch Stufen zur Uebersetzung von graphemischem in phonetischen Text, zur Generierung der Sprachmelodie durch den Text usw.
Die Stufen komplett waeren also in etwa folgende:
* Einlesen des Textes mit evtl. Korrektur des Zeichensatzes/der Zeichendarstellung.
* Praeprozessor zur Textvorbereitung durch Ausschreiben von Zahlen in Woerter usw. (davon handelt dieser Abschnitt hier).
* Uebersetzung der Praeprozessor-Ausgabe: Graphemischen Text nach phonetischen Text (hier noch nicht vorhanden).
Diese Stufe teilt sich in zwei Unterstufen auf:
- woerterbuchbasierte Uebersetzung (hier noch nicht vorhanden) und fuer den evtl. noch nicht uebersetzten Rest
- regelbasierte Uebersetzung (hier noch nicht vorhanden).
* Der phonetische Text enthaelt neben generierten Silbenbetonungszeichen noch die vom Praeprozessor hinterlassenen Satzzeichen.
Anhand dieser Zeichen wird nun die Sprachmelodie generiert und entsprechende Steuerbefehle fuer den Synthesizer in den Text eingefuegt.
* Synthesizer (vorhanden, siehe unten bei PSpSyn).
erzeugt Audio-Signal.
* Ausgabe (zur Soundkarte oder als Web-Stream zum Beispiel, hier nicht vorhanden).

Dieses "Kapitel" beschraenkt sich nur auf den Praeprozessor.

Der Praeprozessor hier, den man weiter unten downloaden kann, erwartet einen Text aus den Buchstaben A-Z, a-z, Ä, Ö, Ü, ä, ö, ü und ß.
Alle anderen Zeichen ausserhalb des ASCII-Bereichs, egal ob Buchstabe oder nicht, werden einfach geloescht.

Mit dem Text passiert sonst noch folgendes:
Der Gesamttext wird in (Teil-)Saetze, nachfolgend einfach nur Saetze/Satz genannt, aufgeteilt und diese in einer eigenen Zeile ausgegeben.
Ein Satz endet mit einem Punkt, einem Ausrufezeichen oder einem Fragezeichen.
Saetze, die eine Ueberlaenge erreichen, koennen auch schon vorher "beendet" werden, dann nach einem Komma, einem Semikolon, einem Doppelpunkt, einem Bindestrich, Leerzeichen oder Zeilentrenner oder, im Notfall auch mitten im Wort.
Ein Rest eines evtl. abgeschnittenen Teils eines Satzes geht nicht verloren, sondern wandert einfach nur in eine Folgezeile.

Weitere Umwandlungen:
* Vor der Ausgabe der Saetze werden, sofern vorhanden, ein paar wenige Abkuerzungen wie "usw." ausgeschrieben.
Bei einigen Abkuerzungen bleibt der evtl. vorhandene Punkt erhalten, wodurch der Satz hier (eigentlich faelschlicherweise) endet - Sprachausgaben sind dumm und verstehen den Inhalt eines Textes nun mal nicht (solche Fehler sind aber im Rahmen des Ertraeglichen).
* Zahlen werden in Zahlwoerter uebersetzt, also z. B. "123" in "einhundertdreiundzwanzig".
Bei zu grossen Zahlen oder solchen, die mit einer 0 beginnen, werden einfach nur die Ziffern in Zahlwoerter uebersetzt: "0123" wird bspw. zu "null eins zwei drei".
In dem kleinen Praeprozessor hier finden sonst keine Zahluebersetzungen mehr statt, also z. B. auch keine Zeit-/Datumsuebersetzungen.
"24.12.2015" wird also schlicht zu "vierundzwanzig.", "zwoelf." und "zweitausendfuenfzehn (und weitere Woerter falls Satz nicht beendet)".
* Ausserdem werden noch ein paar Satzzeichen entweder geloescht oder als Woerter uebersetzt.
Zeichen wie $, # oder % werden immer in ein Wort uebersetzt.
Zeichen wie das Apostroph werden auch in ein Wort uebersetzt ausser wenn es am Wortende steht, dann wird es geloescht (durch einfache Aenderung der entsprechenden Tabelle im Quelltext kann das auch bspw. in der Wortmitte passieren).
* Zeichen wie Tabulatoren und Zeilentrenner werden in Leerzeichen uebersetzt und mehrfache Leerzeichen durch ein einzelnes.
Leerzeichen am Anfang oder am Ende eines Satzes werden geloescht.

Das Ergebnis des Praeprozessors ist eine Liste von Saetzen, die keine Zahlen und "unbekannte" Zeichen mehr enthalten, bei denen (einige) Abkuerzungen als Wort ausgeschrieben und die Leerzeichen minimiert wurden.
Als Satzzeichen kommen nur noch Komma, Punkt, Ausrufezeichen und Fragezeichen vor, als Leerzeichen nur noch das ASCII-Leerzeichen (Code 32).
Es ist moeglich dass Leerzeilen ausgegeben werden und je nach Eingabe natuerlich auch, dass Saetze keinen Abschluss (Punkt usw.) haben.
Bei unveraenderten Textteilen bleibt die Gross-/Kleinschreibung erhalten (sonst kann das schon mal etwas wuest/falsch sein).

Die Textausgabe kann nun an andere (noch zu schreibende) TTS-Stufen weitergeleitet werden.

Verwendung des Praeprozessors:

require "preproc"
ausgabeliste = preproc(textstring)
for i = 1, #ausgabeliste do print(ausgabeliste[i]) end -- Beispiel

Falls ein Fehler 
"setlocale failed."
beim Start des Demos auftritt:
Dann bitte am Anfang des Quelltextes preproc.lua folgendes nach Bedarf anpassen:
local language = "German"
language country = "Germany"
local codepage = "OCP"
local seperator = "_"
Dummerweise muss das je nach Betriebssystem etwas anders aussehen.
Statt "German" kann man "de" (in Klein- oder Grossbuchstaben) ausprobieren,
statt "Germany" kann man "DE" (in Gross- oder Kleinbuchstaben) ausprobieren,
statt dem Seperator "_" den Seperator "-",
statt der Codepage "OCP" oder "ACP" auch sowas wie "1152" oder "850"; "UTF8" oder sowas wird aber nicht funktionieren, da der Praeprozessor nur mit 1-Byte-pro-Zeichen arbeitet.
Die Codepage zu korrigieren kann auch noetig sein, wenn der Praeprozessor die Umlaute nicht richtig erkennt.

Es macht keinen Sinn, den Praeprozessor auf eine andere Sprache als Deutsch zu setzen.
Er wird als Programm fuer die deutsche Sprache nicht die russischen, tuerkischen, franzoesischen ... Eigenheiten beruecksichtigen, sondern entweder einfach beim Deutschen bleiben oder Unsinn veranstalten.

Auch wenn man mit dem Praeprozessor alleine noch nicht so viel anfangen kann, das TTS-System also noch sehr unvollstaendig ist, hier der Download (inklusive Mini-Demo):

http://www.erbsenkopf.de/preproc.zip


Nachtraegliche Anmerkungen:
* Der Praeprozessor ist kaum getestet, kann also durchaus noch echte Fehler enthalten.
* Im Quelltext verwende ich fuer die Umlaute in den "Woerterbuechern" usw. eine besondere Schreibweise:
:A fuer Ä, :O fuer Ö usw.
und es gibt eine Funktion xde, die diese Schreibweise in echte Umlaute wandelt.
Dadurch kann der Quelltext auf reine ASCII-Zeichen beschraenkt bleiben und die Woerterbucheintraege usw. bleiben trotzdem noch halbwegs lesbar und Zeichen-Codes wuerden ohnehin nicht unbedingt stimmen.
* Im Quelltext gibt es keine Einrueckungen. Das ist so wie es ist. :-)


SpSyn2015
=========

Vorweg:
Ja ich schreibe schneller als ich denke. :-)
Korrektur (keinen Bock das da unten zu korrigieren).
PSpSyn2015 benoetigt nicht weniger, sondern *mehr* Bandbreite als die 2012er-Version, weil es naemlich 64kbps beansprucht und die Samplerate liegt bei 24kHz.
Der Fehler in der Beschreibung steht so auch noch in der readme.txt und bleibt da auch so... nerviges Hin- und Hergeschaufel von dem Zeug macht mich voellig krank. :-)
Mp3SpSyn, SpSyn, PSpSyn ist alles das gleiche - habe ein schlechtes Namensgedaechtnis. :-)

Phonetische Sprachausgabe mit variabler Sprechgeschwindigkeit und -tonlage in 100 Zeilen Lua-Code! :-)

Ui! Wie die Zeit vergeht! 2012 hatte ich damit rumgespielt... :-o

Also, es gibt nochmal eine phonetische mp3-Sprachausgabe.
Die ist jetzt einfacher/einleuchtender/uebersichtlicher, klingt zwar nicht unbedingt verstaendlicher als die unten von 2012, aber dieser olle reverb-artige Effekt ist weg und die Sampleraten stimmen (klingt in der alten Version unten zu tief).
Die hier produziert nun 24 kbps, braucht also auch weniger Bandbreite.
Damit aber noch nicht genug, denn die Phonemdatei ist auf etwas unter 3 MB geschrumpft; das ist glaube ich ca. 1/4 oder 1/3 der Groesse von dem Vorgaenger (weiss gerade nicht deren genaue Groesse).
Dafuer gibt's hier nur ein zip-Archiv mit der Phonemdatei und einem bloeden Demo, und diesmal auch nur in Lua 5.2 - die paar Zeilen sind leicht nach Python, PHP, Perl oder sonst was zu portieren. :-) Link siehe weiter unten.

Kurz noch der Aufbau der Phonemdatei:

Die Phonemdatei enthaelt zuerst 28 Phonembloecke, naemlich fuer die Phoneme
2 6 9 @ a A D e E i I j l L m n N o O r R u U v y Y z Z (entsprechen im wesentlichen den deutschen Mbrola-Phoneme; deren Bedeutung liste ich unten noch auf).
Jeder Phonemblock besteht aus 8 mp3-Frames zu je 192 Bytes.
Obige Phoneme sind stimmhafte Phoneme und obige Sequenz ist deshalb 64 mal hintereinander in absteigender Tonlage abgelegt.
Danach kommen nur noch stimmlose Phoneme, die deshalb auch jeweils nur einmal vorkommen (haben auch jeweils 8 Frames zu je 192 Bytes).
Die stimmlosen Phoneme teilen sich in die Rauschphoneme plus ein Pausenphonem einerseits und die sogenannten Plossive andererseits auf:
C f h q s S T x (Rauschen und Pause q)
b d g k p t (Plossive)

Und nun das ganze nochmal als C-Typ-Definition - ist vielleicht verstaendlicher (dabei auch gleich die Bedeutung der Phoneme):

typedef char mp3frame_t[192]; // nicht generell, aber fuer diese Datei

typedef mp3frame_t phonemblock_t[8]; // 8 mp3-Frames fuer ein Phonem

typedef struct {
struct {
phonemblock_t _2; // OEkonom
phonemblock_t _6; // vatER
phonemblock_t _9; // OEffnung
phonemblock_t _at; // (@) hebEn
phonemblock_t _a; // Anton
phonemblock_t _A; // hier identisch mit a
phonemblock_t _D; // engl. faTHer
phonemblock_t _e; // Emil
phonemblock_t _E; // AEgypten
phonemblock_t _i; // Ida
phonemblock_t _I; // kIppe
phonemblock_t _j; // Julius
phonemblock_t _l; // Ludwig
phonemblock_t _L; // engl. Left
phonemblock_t _m; // Marta
phonemblock_t _n; // Nordpol
phonemblock_t _N; // fiNGer
phonemblock_t _o; // ottO
phonemblock_t _O; // Otto
phonemblock_t _r; // engl. Right
phonemblock_t _R; // Richard
phonemblock_t _u; // Uwe
phonemblock_t _U; // Ulrich
phonemblock_t _v; // Viktor, Wasser
phonemblock_t _y; // sUEden
phonemblock_t _Y; // hUEfte
phonemblock_t _z; // Samstag
phonemblock_t _Z; // Garage, Journal
} stimmhaft[64]; // fuer jeden der moeglichen 64 Pitchwerte
struct {
phonemblock_t _C; // beCHer
phonemblock_t _f; // Fritz
phonemblock_t _h; // Heinrich
phonemblock_t _q; // Das ist das Pausenphonem ("Stille")
phonemblock_t _s; // hauS
phonemblock_t _S; // SCHule
phonemblock_t _T; // engl. norTH
phonemblock_t _x; // flaCH
} stimmlos;
struct {
phonemblock_t _b; // Berta
phonemblock_t _d; // Dora
phonemblock_t _g; // Gustl
phonemblock_t _k; // Kaufmann
phonemblock_t _p; // Paula
phonemblock_t _t; // Theo
} plossive;
} phonemdatenbank_t;

Die Sprache kann nicht grossartig in der Geschwindigkeit veraendert werden.
3 Frames fuer unverlaengerte Phoneme, die keine Plossive sind, hat ein angenehmes Sprechtempo.
2 ist schon deutlich schneller und
1 klingt eigentlich unbrauchbar, auch wenn man es mit Uebung verstehen kann.
Verlaengerte Vokale, koennen z. B. um die Haelfte der Kurzvariante verlaengert werden, also bei obiger "Stufe" 3 waeren das dann 4 oder 5 Frames.
Die Plossive haben 3 Frames (natuerlich auch 8 Frames in der Phonemdatei, die hinteren 5 Frames sind aber Fuellframes=Pause) und sollten auch immer vollstaendig ausgegeben werden.
Es kann jedoch sinnvoll sein, je nach Sprechtempo, evtl. ein Pausen-Frame voranzustellen und ein/zwei hinten dran zu haengen.

Wie koennen Tempo und Sprachtonlage veraendert werden:

Im phonetischen Text durch
/p<nn> : Pitch 1 (hoch) bis 64 (tief) setzen; 35 ist in etwa die Normaltonlage. <nn> ohne die spitzen Klammern ist eine 2stellige Dezimalzahl.
/<n> : Kurzform von /p mit groeberem Abstand der Pitchwerte (spart ggf. Tipperei). <n> ist eine einstellige Dezimalzahl.
/s<n> : Sprechgeschwindigkeit 1 (zu schnell) bis 5 (langsam); 3 ist in etwa das Normalsprechtempo. <n> ist eine einstellige Dezimalzahl.
/l<n> : legt die Dauer fuer durch einen Doppelpunkt verlaengerte Phoneme fest (1 bis 8 Frames). /s setzt auch den Wert von /l, nicht aber umgekehrt.
' : Pitch um eine Stufe erhoehen
, : Pitch um eine Stufe vermindern
. : Pitch auf Normalwert zuruecksetzen
< : Sprechtempo um eine Stufe verlangsamen
> : Tempo um eine Stufe beschleunigen
= : Tempo auf Normalwert zuruecksetzen

Ausserdem:

Zeilenvorschub, Leerzeichen oder Tabulator entsprechen dem Pausen-Phonem q.
Der Unterstrich (_) entspricht einer 0-Pause, hat also keine hoerbare Funktion.
Das Minuszeichen entspricht zwei mal q, also qq.
Ein Doppelpunkt (:) hinter einem Phonem verlaengert dieses um etwa die Haelfte (nur bei stimmhaften Phonemen, wird anderenfalls ignoriert).

Verwendung von PSpSyn siehe Mini-Demo im Archiv - simpel!

Ok, hier also die Phonemdatei und einem kleinen Beispiel-Script, beides in Lua:
http://www.erbsenkopf.de/pspsyn2015.zip

Zum Anhoeren noch die Ausgabe des im Archiv enthaltenen Demos:
http://www.erbsenkopf.de/pspsyndemo.mp3

Nachtrag: Die Phonemdatei sollte genau 192 * 8 * (64 * 28 + 8 + 6) Bytes umfassen, was auch sizeof(phonemdatenbank_t) von oben entsprechen sollte.

Noch ein Nachtrag: Dass die Quelldateien keine oder nur einstellige Einrueckungen haben ist so wie es ist. :-)

SpSyn von mir aus zur freiesten Verfuegung.


mp3spsyn 12.2012
Phonetische Sprachausgabe mit mp3-Ausgabe
(im Rahmen eines Experiments auf die Schnelle zusammengebastelt)

Zuviel sollte man nicht erwarten; es war nur ein Experiment, wie gesagt.

Download (Python-Quelltext)
Groesse: ca. 14 MB (bedingt durch die grosse Phonemdatei)
Download (kleines Update#1)
Groesse: ca. 2 KB. Die hier enthaltenen Dateien ueberschreiben obige (nur phoneme.txt).


Hier noch eine sprechende Uhr zum Download (benoetigt erstes Archiv wegen Phonemdatei)
...und hier die sprechende Uhr als Online-Demo

Unterstuetzt ueberwiegend deutsche Phoneme,
einige englische Behelfsphoneme sind vorhanden und
das franzoesische stimmhafte SCH wie in Journal.

Zum Ausgabeformat:
.wav oder LPC ist eindeutig besser fuer solche Dinge geeignet als MP3
(jedenfalls in der hier durchgefuehrten Art und Weise).
MP3 wird allerdings von den meisten Medienspielern unterstuetzt
(im Gegensatz zu irgendwelchen LPC-Formaten) und benoetigt relativ
wenig Netzbandbreite (im Gegensatz zu .wav in dieser 
"kompatiblen Qualitaetsklasse").

Ausgegeben wird hier MP3, 32kbps, 16kHz, mono.

Zur Verwendung werden, neben dem von Dir selbstgeschriebenen Programm, benoetigt:
1. mp3spsyn.py und
2. mp3photab.dat.
Ein Beispiel-Prograemmchen (example.py) ist hier vorhanden, das
ine.txt nach out.mp3 konvertiert (synthetisiert).

Zur Erstellung von geeigneten phonetischen Quellsprachtexten
(das Format, das auch von Mbrola verwendet wird)
eignet sich tx2pho oder eSpeak,
falls man nicht selbst einen Deutsch->Phonem-Uebersetzer schreiben moechte.

Geschrieben und mehr oder weniger getestet mit Python 2.7.
Python 3 sollte funktionieren (?).
Besondere Bibliotheken werden nicht benoetigt - das ist ja das tolle. :-)

Von mir aus zur freiesten Verfuegung.
Viel Spass damit

PS:
Bezueglich des Beispielgedichts:
Natuerlich habe ich nichts gegen Schuhmacher.
Es ist einfach nur ein Beispielgedicht und das einzige
das ich kann. :-)