Skip to content

Commit 4efe3ad

Browse files
asander-ccswtravisgoodspeed
authored andcommitted
Add Marc4 decoder
* Add Affine decoder, a class that can concisely implement many different decoding orders. A high-level introduction to the affine decoder is in `affinedecoder.h`, while an example of it being used is in `gatodecodermarc4.cpp`. * Implement Marc4 decoder using Affine decoder, resolving #67.
1 parent 97b20fe commit 4efe3ad

File tree

12 files changed

+320
-2
lines changed

12 files changed

+320
-2
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ set(GATOROM_SOURCES
9090
gatodecodertlcsfont.h gatodecodertlcsfont.cpp # TMP47C434N Font ROM
9191
gatodecoderz86x1.h gatodecoderz86x1.cpp # Zilog Z8 Z86x1
9292
gatodecodercolsdownlswap.h gatodecodercolsdownlswap.cpp # Used in NEC uCOM4 Micros
93+
gatodecodermarc4.h gatodecodermarc4.cpp
94+
affinedecoder.h # generic affine decoder
9395
# Decoder named after Zorrom strategies.
9496
gatodecodercolsdownr.h gatodecodercolsdownr.cpp # Top-to-bottom then left to right, 8-bits.
9597
gatodecodercolsdownl.h gatodecodercolsdownl.cpp

affinedecoder.h

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#ifndef AFFINEDECODER_H
2+
#define AFFINEDECODER_H
3+
4+
#include "gatorom.h"
5+
#include <array>
6+
/* This is a general purpose "affine decoder". It aims to
7+
make a template for a good variety of decoder bit patterns
8+
with only small code changes. There are 3 arrays used to construct it:
9+
- counts: each entry indicates how many times the stride should be repeated
10+
- col_strides: how much to increment col by each time this stride is taken
11+
- row_strides: how much to increment row by each time this stride is taken
12+
13+
The strides act like an affine map, the column increment and row increment are
14+
multiplied by an index vector to produce a row offset and column offset.
15+
The index vector increments based on the counts, incrementing idx_vec[0]
16+
until it reaches count[0], at which point it "carries" over to increment
17+
idx_vec[1]. As an example, suppose counts = [2, 3, 4]. The index vector would
18+
increment through: [0,0,0] [1,0,0] [0,1,0] [1,1,0] [0,2,0] [1,2,0] [0,0,1]
19+
20+
Combining this with the row and column strides, it's possible to represent many
21+
interleaved and non-interleaved bit-read patterns.
22+
23+
A simple layout:
24+
1234
25+
5678
26+
27+
would look like
28+
counts: [4, 2]
29+
col_strides: [1, 0]
30+
row_strides: [0, 1]
31+
32+
and would iterate as
33+
idx vector: col, row
34+
00: (0*1)+(0*0), (0*0)+(0*1)
35+
...
36+
30: (3*1)+(0*0), (3*0)+(0*1)
37+
01: (0*1)+(1*0), (0*0)+(1*1)
38+
...
39+
31: (3*1)+(1*0), (3*0)+(1*1)
40+
41+
42+
colsdownl can be represented with:
43+
initial col offset: numcols-numcols/wordsize
44+
initial row offset: 0
45+
counts : [wordsize , numrows, numcols/wordsize]
46+
col_strides: [-numcols/wordsize, 0 , 1]
47+
row_strides: [0 , 1 , 0]
48+
49+
so for example with wordsize=2, numrows=4, numcols=4:
50+
1908
51+
3b2a
52+
5d4c
53+
7f6e
54+
55+
so:
56+
counts: [2,4,2]
57+
col_strides: [-2,0,1]
58+
row_strides: [0,1,0]
59+
idx: col, row
60+
000: 2, 0
61+
100: 2-2+0+0, 0+0+0
62+
010: 2+0+0+0, 0+1+0
63+
110: 2-2+0+0, 0+1+0
64+
020: 2+0+0+0, 0+2+0
65+
120: 0,2
66+
030: 2,3
67+
130: 0,3
68+
001: 2+0+0+1, 0+0+0
69+
101: 2-2+0+1, 0+0+0
70+
...
71+
72+
*/
73+
template <size_t N> class AffineDecoder : public GatoDecoder {
74+
private:
75+
std::array<unsigned int, N> counts;
76+
std::array<int, N> row_strides;
77+
std::array<int, N> col_strides;
78+
int row_start;
79+
int col_start;
80+
81+
public:
82+
AffineDecoder(std::array<unsigned int, N> counts,
83+
std::array<int, N> row_strides, std::array<int, N> col_strides,
84+
int row_start, int col_start);
85+
void decode(GatoROM *gr);
86+
void inc_idx_counts(std::array<unsigned int, N> &idx_counts);
87+
int get_row_offset(std::array<unsigned int, N> &idx_counts);
88+
int get_col_offset(std::array<unsigned int, N> &idx_counts);
89+
};
90+
91+
template <size_t N>
92+
AffineDecoder<N>::AffineDecoder(std::array<unsigned int, N> counts,
93+
std::array<int, N> row_strides,
94+
std::array<int, N> col_strides, int row_start,
95+
int col_start) {
96+
97+
this->row_start = row_start;
98+
this->col_start = col_start;
99+
this->name = "Raw Affine Decoder";
100+
this->counts = counts;
101+
this->row_strides = row_strides;
102+
this->col_strides = col_strides;
103+
}
104+
105+
template <size_t N>
106+
int AffineDecoder<N>::get_col_offset(std::array<unsigned int, N> &idx_counts) {
107+
int col_offset = this->col_start;
108+
for (size_t i = 0; i < N; i++) {
109+
col_offset += idx_counts[i] * this->col_strides[i];
110+
}
111+
return col_offset;
112+
}
113+
114+
template <size_t N>
115+
int AffineDecoder<N>::get_row_offset(std::array<unsigned int, N> &idx_counts) {
116+
int row_offset = this->row_start;
117+
for (size_t i = 0; i < N; i++) {
118+
row_offset += idx_counts[i] * this->row_strides[i];
119+
}
120+
return row_offset;
121+
}
122+
123+
template <size_t N>
124+
void AffineDecoder<N>::inc_idx_counts(std::array<unsigned int, N> &idx_counts) {
125+
size_t cur_idx = 0;
126+
idx_counts[cur_idx]++;
127+
cur_idx++;
128+
while (cur_idx < N &&
129+
(idx_counts[cur_idx - 1] == this->counts[cur_idx - 1])) {
130+
idx_counts[cur_idx - 1] = 0;
131+
idx_counts[cur_idx]++;
132+
cur_idx++;
133+
}
134+
};
135+
136+
template <size_t N> void AffineDecoder<N>::decode(GatoROM *gr) {
137+
138+
uint32_t adr = 0;
139+
QByteArray ba, bad; // data and damage
140+
141+
int wordsize = gr->wordsize;
142+
std::array<unsigned int, N> idx_counts = {0};
143+
gr->eval();
144+
145+
unsigned int num_bits = gr->outputrows * gr->outputcols;
146+
if (num_bits % wordsize != 0) {
147+
return;
148+
}
149+
// we'll go bit-by-bit, grouping by words
150+
unsigned int num_words = num_bits / wordsize;
151+
for (int word = 0; word < num_words; word++) {
152+
uint32_t w = 0, wd = 0;
153+
154+
for (int bit = wordsize - 1; bit >= 0; bit--) {
155+
unsigned int row = this->get_row_offset(idx_counts);
156+
unsigned int col = this->get_col_offset(idx_counts);
157+
this->inc_idx_counts(idx_counts);
158+
GatoBit *B = gr->outputbit(row, col);
159+
assert(B);
160+
161+
B->adr = adr;
162+
B->mask = 1 << bit;
163+
if (B->getVal())
164+
w |= B->mask;
165+
if (B->ambiguous)
166+
wd |= B->mask;
167+
}
168+
169+
// This is implicitly little endian
170+
for (int bitcount = wordsize; bitcount > 0; bitcount -= 8) {
171+
ba.append(w & 0xFF);
172+
w = w >> 8;
173+
bad.append(wd & 0xff);
174+
wd = wd >> 8;
175+
176+
adr++;
177+
}
178+
}
179+
gr->decoded = ba;
180+
gr->decodedDamage = bad;
181+
}
182+
#endif // AFFINEDECODER_H

gatodecodermarc4.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "gatodecodermarc4.h"
2+
#include "affinedecoder.h"
3+
#include <array>
4+
5+
/* This decoder is based on the layout of MARC4 mask ROMS
6+
* MARC4 ROMS are split up into "meta columns" where each
7+
* meta column contains 2 interleaved columns of words.
8+
* Specifically, on MARC4, each meta column consists of
9+
* 16 columns and 2 words. A word consists of bits from
10+
* every-other column in a meta column, with the next word
11+
* being on the row below the current word. Once the last row
12+
* has been read, the next word is at the top of the next column,
13+
* or the top of the next meta-column, if this is the last
14+
* column in the current meta-column. For example, with a
15+
* word size of 2 bits and 2 meta columns, and 4 rows, the bit ordering
16+
* would look like:
17+
* 0 4 0 4 8 c 8 c
18+
* 1 5 1 5 9 d 9 d
19+
* 2 6 2 6 a e a e
20+
* 3 7 3 7 b f b f
21+
* In the above chart, number indicates which word that bit is a member of.
22+
* The dimensions of the ROM and word size for the chart above are smaller than
23+
* are realistic in order to keep the chart small. With a word size of 8 and 4
24+
* rows, the leftmost meta-column would have bit indices:
25+
* 0 4 0 4 0 4 0 4 0 4 0 4 0 4 0 4
26+
* 1 5 1 5 1 5 1 5 1 5 1 5 1 5 1 5
27+
* 2 6 2 6 2 6 2 6 2 6 2 6 2 6 2 6
28+
* 3 7 3 7 3 7 3 7 3 7 3 7 3 7 3 7
29+
*/
30+
GatoDecoderMarc4::GatoDecoderMarc4() { name = "marc4"; }
31+
32+
void GatoDecoderMarc4::decode(GatoROM *gr) {
33+
unsigned int nrows = gr->outputrows;
34+
unsigned int ncols = gr->outputcols;
35+
unsigned int wordsize = gr->wordsize;
36+
if (ncols % (2 * wordsize) != 0) {
37+
return;
38+
}
39+
std::array<unsigned int, 4> counts = {wordsize, nrows, 2, ncols / 16};
40+
std::array<int, 4> col_strides = {2, 0, 1, (int)wordsize * 2};
41+
std::array<int, 4> row_strides = {0, 1, 0, 0};
42+
AffineDecoder<4> root_decode =
43+
AffineDecoder(counts, row_strides, col_strides, 0, 0);
44+
return root_decode.decode(gr);
45+
}

gatodecodermarc4.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef GATODECODERMARC4_H
2+
#define GATODECODERMARC4_H
3+
4+
#include "affinedecoder.h"
5+
#include "gatorom.h"
6+
7+
class GatoDecoderMarc4 : public GatoDecoder {
8+
public:
9+
GatoDecoderMarc4();
10+
void decode(GatoROM *gr);
11+
};
12+
13+
#endif // GATODECODERMARC4_H

gatomain.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "gatodecodertlcsfont.h"
2020
#include "gatodecoderz86x1.h"
2121
#include "gatodecodercolsdownlswap.h" // NEC uCOM4
22+
#include "gatodecodermarc4.h" //MARC4
2223

2324
//Zorrom compatibility.
2425
#include "gatodecodercolsdownr.h"
@@ -186,6 +187,11 @@ int main(int argc, char *argv[]) {
186187
);
187188
parser.addOption(zorromOption);
188189

190+
QCommandLineOption marc4Option(QStringList()<<"decode-marc4",
191+
"Decodes column-major, 2-bit column interleave as for MARC4 ROM"
192+
);
193+
parser.addOption(marc4Option);
194+
189195
//banking
190196
QCommandLineOption leftbankOption(QStringList()<<"leftbank",
191197
"Only the left half of the bits."
@@ -389,6 +395,8 @@ int main(int argc, char *argv[]) {
389395
gr->decoder=new GatoDecoderColsRight();
390396
else if(parser.isSet(squeezelrOption))
391397
gr->decoder=new GatoDecoderSqueezeLR();
398+
else if (parser.isSet(marc4Option))
399+
gr->decoder = new GatoDecoderMarc4();
392400

393401

394402

gatorom.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "gatodecodertlcsfont.h"
1111
#include "gatodecoderz86x1.h"
1212
#include "gatodecodercolsdownlswap.h" // NEC uCOM4
13+
#include "gatodecodermarc4.h" //MARC4
1314
//Zorrom compatibility.
1415
#include "gatodecodercolsdownr.h"
1516
#include "gatodecodercolsdownl.h"
@@ -184,6 +185,8 @@ void GatoROM::setDecoderByName(QString name){
184185
decoder=new GatoDecoderColsRight();
185186
else if(name=="squeeze-lr")
186187
decoder=new GatoDecoderSqueezeLR();
188+
else if(name=="marc4")
189+
decoder=new GatoDecoderMarc4();
187190
else
188191
qDebug()<<"Unknown decoder"<<name;
189192
}

gatosolver.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include "gatodecodertlcsfont.h"
1212
#include "gatodecodercolsdownlswap.h" //NEC uCOM4
1313
#include "gatodecoderz86x1.h"
14-
14+
#include "gatodecodermarc4.h" // marc4
1515

1616
// Default solver is a dummy state that covers everything.
1717
GatoGraderAll::GatoGraderAll(){}
@@ -41,8 +41,8 @@ GatoSolver::GatoSolver(GatoROM *rom, GatoGrader *grader){
4141
decoders[5]=new GatoDecoderTLCSFont();
4242
decoders[6]=new GatoDecoderZ86x1();
4343
decoders[7]=new GatoDecoderColsDownLSwap(); //NEC uCOM4
44+
decoders[8]=new GatoDecoderMarc4();
4445
//Remainder of table must be null.
45-
decoders[8]=0;
4646
decoders[9]=0;
4747
decoders[10]=0;
4848
decoders[11]=0;

gatotests/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ all:
55
make -C gameboy all
66
make -C seanriddle all
77
make -C z8 all
8+
make -C marc4 all
89
clean:
910
make -C zorrom clean
1011
make -C arm6 clean
1112
make -C tlcs clean
1213
make -C gameboy clean
1314
make -C seanriddle clean
1415
make -C z8 clean
16+
make -C marc4 clean
1517

gatotests/marc4/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
all: test
2+
3+
marc4-topleft-bits.bin:
4+
gatorom --decode-marc4 marc4-topleft-bits.txt -o marc4-topleft-bits.bin
5+
6+
test: marc4-topleft-bits.bin
7+
md5sum -c md5.txt
8+
9+
clean:
10+
rm -f *.bin
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
0001010101000111101001110101111000100111111111100000111111111100
2+
0111011101000010010001110101011010100101010101100000111111111100
3+
0100101111111100101001011101010000001111111111000000111111111100
4+
0101111111111000101001111101110000001111111111000000111111111100
5+
0101101010101000101011010101010000001111111111000000111111111100
6+
0101101111101001111110010100010000001111111111000000111111111100
7+
0101111110111000100000100010101000001111111111000000111111111100
8+
0101101010101000111000110100100100001111111111000000111111111100
9+
1111001110001100010010111010100000001111111111000000111111111100
10+
0000000000010001000011111111100000001111111111000000111111111100
11+
1101001101101000010010111110110100001111111111000000111111111100
12+
1101001101000000010010111010100000001111111111000000111111111100
13+
0101111011110011010010111010100000001111111111000000111111111100
14+
1100011111111011000010101010100000001111111111000000111111111100
15+
1000001010101010111000111101100000001111111111000000111111111100
16+
1110001101000011111010010100000000001111111111000000111111111100
17+
1110001101000110101011010101010000001111111111000000111111111100
18+
1010011101010010101011010101010000001111111111000000111111111100
19+
0110000111101101101001111101010000001111111111000000111111111100
20+
1110100100101100101001111101011000001111111111000000111111111100
21+
0100000101100110100001110111010000001111111111000000111111111100
22+
0000111011001001111101100000111000001111111111000000111111111100
23+
0100010111110111111101110000101100001111111111000000111111111100
24+
0101000101000100110000100110000000001111111111000000111111111100
25+
1000011010111001000010111110100100001111111111000000111111111100
26+
1100001100000101111010010101100100001111111111000000111111111100
27+
1111011110001101111110110001001100011111111111000000111111111100
28+
1010110111100000100100011100000100001111111111000000111111111100
29+
1100001101110010010101101001011000001111111111000000111111111100
30+
0100100110000110110000101000010100001111111111000000111111111100
31+
1101011011100110110101001011000100001111111111000000111111111100
32+
0000000100000001111010110101100100001111111111000000111111111100
33+
1101001001101000010010111111110100001111111111000000111111111100
34+
1101001110000000010110111010100100001111111111000000111111111100
35+
0001111010010010000110111111110000001111111111000000111111111100
36+
1101011010000111111101111100100000001111111111000000111111111100
37+
1100001010001111010000010100011000001111111111000000111111111100
38+
0101000101010001100100110110100100001111111111000000111111111100
39+
1100001100110111110101100001011000001111111111000000111111111100
40+
1110100000000101010010001000011100001111111111000000111111111100
41+
1101011000100011110101101010001100001111111111000000111111111100
42+
0100000101010101010000010001010100001111111111000000111111111100
43+
1100001101101101110000110111110100011111111111000000111111111100
44+
1000001001000001110100111000000100001111111111000000111111111100
45+
1110001101000110000111101001001000001111111111000000111111111111
46+
0111001100000011010101010100000000001111111111100000111111111100
47+
1111101110000001110000110111001000001111111111000000111111111100

0 commit comments

Comments
 (0)