Line Encoding Bitstreams with Ragel
Ragel is a neat code generator for compiling efficient state machine programs. A quick way to describe it is "regular expressions on steroids". The input is essentially regular expressions interspersed with C code that will run whenever the corresponding transistions are taken.
I wanted to practice using it and thought that the line codes
described on Wikipedia would provide very simple state machines to try
out. The output of this program provides input to WaveDrom, which is
another fun tool for creating timing diagrams from text. In WaveDrom,
h
means "high state", l
means "low state", and .
means hold the
previous state.
Here's the source (lc.rl):
/* lc -- Line encode a bitstream with output in WaveDrom format. Copyright (C) 2022 Remington Furman 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 3 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, see <https://www.gnu.org/licenses/>. */ #include <stdio.h> #include <string.h> #include <getopt.h> #include <stdlib.h> struct line_code { int cs; /* Current state. */ int bit; /* Current bit value. */ }; %%{ machine line_code; access fsm->; action low { fsm->bit = 0; putchar('l'); } action high { fsm->bit = 1; putchar('h'); } action toggle { fsm->bit ^= 1; putchar(fsm->bit ? 'h' : 'l'); } action toggle2 { fsm->bit ^= 1; putchar(fsm->bit ? 'h' : 'l'); fsm->bit ^= 1; putchar(fsm->bit ? 'h' : 'l'); } action hold { putchar('.'); } action nl { putchar('\n'); } zero_low_h = ('0' @low ('0' @hold)*); # Emit one low then hold for rest zero_high_h = ('0' @high ('0' @hold)*); # Emit one high then hold for rest one_low_h = ('1' @low ('1' @hold)*); # Emit one low then hold for rest one_high_h = ('1' @high ('1' @hold)*); # Emit one high then hold for rest # Line code machines nrz_l := ((zero_low_h | one_high_h )** 0 @nl)*; # Non-return-to-zero level nrz_i := ((zero_high_h | one_low_h )** 0 @nl)*; # Non-return-to-zero invert nrz_s := (('0' @toggle | '1' @hold )* 0 @nl)*; # Non-return-to-zero space nrz_m := (('0' @hold | '1' @toggle)* 0 @nl)*; # Non-return-to-zero mark bph_l := (('0' @low @high | '1' @high @low )* 0 @nl)*; # Bi-phase level bph_s := (('0' @toggle2 | '1' @toggle @hold)* 0 @nl)*; # Bi-phase space bph_m := (('0' @toggle @hold | '1' @toggle2 )* 0 @nl)*; # Bi-phase mark # Aliases pass := any @{ fhold; fgoto nrz_l; }; invert := any @{ fhold; fgoto nrz_i; }; manch := any @{ fhold; fgoto bph_l; }; main := any @{ fhold; fgoto nrz_l; }; }%% %% write data; void line_code_init(struct line_code *fsm, int first_bit, int start) { fsm->bit = first_bit; %% write init; fsm->cs = start; } void line_code_execute(struct line_code *fsm, const char *data, int len) { const char *p = data; const char *pe = data + len; %% write exec; } int line_code_finish(struct line_code *fsm) { if (fsm->cs == line_code_error) { return -1; } if (fsm->cs >= line_code_first_final) { return 1; } return 0; } #define NUM_ELEMENTS(a) (sizeof(a)/sizeof(a[0])) #define XSTRINGIFY(s) #s #define STRINGIFY(s) XSTRINGIFY(s) #define MACHINE(name, desc) { STRINGIFY(name), desc, &line_code_en_ ## name } struct { const char *name; char* desc; const int *start; } machines[] = { MACHINE(nrz_l, "Non-return-to-zero level"), MACHINE(nrz_i, "Non-return-to-zero invert"), MACHINE(nrz_s, "Non-return-to-zero space"), MACHINE(nrz_m, "Non-return-to-zero mark"), MACHINE(bph_l, "Bi-phase level"), MACHINE(bph_s, "Bi-phase space"), MACHINE(bph_m, "Bi-phase mark"), MACHINE(pass, "Passthrough (alias of nrz_l)"), MACHINE(invert, "Invert (alias of nrz_i)"), MACHINE(manch, "Manchester (alias of bph_l)"), }; void usage(char *name) { printf("%s [flags] input" , name); printf(" input\n" " One or more strings of 0's and 1's\n" " -i n\n" " Initial line state (default: 0)\n" " -c name\n" " Line code to use (default: pass)\n"); printf("\nAvailable codes:\n"); for (int i = 0; i < NUM_ELEMENTS(machines); i++) { printf(" %-7s- %s\n", machines[i].name, machines[i].desc); } } int main(int argc, char **argv) { struct line_code lc; int first_bit = 0; int start = line_code_en_nrz_l; int opt; while ((opt = getopt(argc, argv, "f:m:h")) != -1) { switch (opt) { case 'f': { first_bit = !!atoi(optarg); break; } case 'm': { int found = 0; for (int i = 0; i < NUM_ELEMENTS(machines); i++) { if (strcmp(optarg, machines[i].name) == 0) { start = *machines[i].start; found = 1; break; } } if (!found) { printf("Invalid machine name: %s\n", optarg); usage(argv[0]); exit(EXIT_FAILURE); } break; } case 'h': { usage(argv[0]); exit(EXIT_SUCCESS); break; } default: { usage(argv[0]); exit(EXIT_FAILURE); break; } } } /* Initialize state machine */ line_code_init(&lc, first_bit, start); for (int i = optind; i < argc; i++) { /* Encode each input argument. */ line_code_execute(&lc, argv[i], strlen(argv[i])+1); } if (line_code_finish(&lc) != 1) { fprintf(stderr, "%s: bad input\n", argv[0]); usage(argv[0]); exit(EXIT_FAILURE); } return 0; }
Here are the generated state machines:
Figure 1: Line encoding state machines
And here is some example output:
$ cat ./lc.sh #!/bin/sh INPUT=01001100011100001111 for machine in nrz_l nrz_i nrz_s nrz_m bph_l bph_s bph_m pass invert manch ; do echo ${machine} $(./lc -m ${machine} ${INPUT}) done $ ./lc.sh pass lhl.h.l..h..l...h... invert hlh.l.h..l..h...l... nrz_l lhl.h.l..h..l...h... nrz_i hlh.l.h..l..h...l... nrz_s h.lh..lhl...hlhl.... nrz_m .h..lh...lhl....hlhl manch lhhllhlhhlhllhlhlhhlhlhllhlhlhlhhlhlhlhl bph_l lhhllhlhhlhllhlhlhhlhlhllhlhlhlhhlhlhlhl bph_s hlh.lhlhl.h.lhlhlhl.h.l.hlhlhlhlh.l.h.l. bph_m h.lhl.h.lhlhl.h.l.hlhlhlh.l.h.l.hlhlhlhl
{signal: [ {name: 'pass', wave: 'lhl.h.l..h..l...h...'}, {name: 'invert', wave: 'hlh.l.h..l..h...l...'}, {name: 'nrz_l', wave: 'lhl.h.l..h..l...h...'}, {name: 'nrz_i', wave: 'hlh.l.h..l..h...l...'}, {name: 'nrz_s', wave: 'h.lh..lhl...hlhl....'}, {name: 'nrz_m', wave: '.h..lh...lhl....hlhl'}, {name: 'manch', wave: 'lhhllhlhhlhllhlhlhhlhlhllhlhlhlhhlhlhlhl'}, {name: 'bph_l', wave: 'lhhllhlhhlhllhlhlhhlhlhllhlhlhlhhlhlhlhl'}, {name: 'bph_s', wave: 'hlh.lhlhl.h.lhlhlhl.h.l.hlhlhlhlh.l.h.l.'}, {name: 'bph_m', wave: 'h.lhl.h.lhlhl.h.l.hlhlhlh.l.h.l.hlhlhlhl'}, ]}
All the code (including a Makefile) can be found here: