openMSX
eeprom.cc
Go to the documentation of this file.
1#include "catch.hpp"
2#include "EmuTime.hh"
3#include "EEPROM_93C46.hh"
4#include "XMLElement.hh"
5#include "xrange.hh"
6
7using namespace openmsx;
8
9static constexpr auto step = EmuDuration::usec(10);
10
11static void input_pattern(EEPROM_93C46& eeprom, EmuTime& time, uint32_t pattern, unsigned len)
12{
13 assert(len <= 32);
14 for (auto i : xrange(len)) {
15 eeprom.write_DI(pattern & (1 << (len - 1 - i)), time);
16 time += step;
17 eeprom.write_CLK(true, time);
18 time += step;
19 eeprom.write_CLK(false, time);
20 time += step;
21 }
22}
23
24static void write(EEPROM_93C46& eeprom, EmuTime& time, unsigned addr, uint8_t value)
25{
26 assert(addr < EEPROM_93C46::NUM_ADDRESSES);
27
28 eeprom.write_CS(true, time);
29 time += step;
30
31 uint32_t pattern = 0b101; // start-bit + write-opcode
32 pattern <<= EEPROM_93C46::ADDRESS_BITS;
33 pattern |= addr;
34 pattern <<= EEPROM_93C46::DATA_BITS;
35 pattern |= value;
36 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS + EEPROM_93C46::DATA_BITS;
37 input_pattern(eeprom, time, pattern, len);
38
39 eeprom.write_CS(false, time);
40 time += step;
41}
42
43static void write_all(EEPROM_93C46& eeprom, EmuTime& time, uint8_t value)
44{
45 eeprom.write_CS(true, time);
46 time += step;
47
48 uint32_t pattern = 0b100; // start-bit + write-opcode
49 pattern <<= EEPROM_93C46::ADDRESS_BITS;
50 pattern |= 0b0100000; // 0b01xxxxx
51 pattern <<= EEPROM_93C46::DATA_BITS;
52 pattern |= value;
53 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS + EEPROM_93C46::DATA_BITS;
54 input_pattern(eeprom, time, pattern, len);
55
56 eeprom.write_CS(false, time);
57 time += step;
58}
59
60static void write_enable(EEPROM_93C46& eeprom, EmuTime& time)
61{
62 eeprom.write_CS(true, time);
63 time += step;
64
65 uint32_t pattern = 0b100; // start-bit + '00'-opcode
66 pattern <<= EEPROM_93C46::ADDRESS_BITS;
67 pattern |= 0b1100000; // 0b11xxxxx
68 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
69 input_pattern(eeprom, time, pattern, len);
70
71 eeprom.write_CS(false, time);
72 time += step;
73}
74
75static void write_disable(EEPROM_93C46& eeprom, EmuTime& time)
76{
77 eeprom.write_CS(true, time);
78 time += step;
79
80 uint32_t pattern = 0b100; // start-bit + '00'-opcode
81 pattern <<= EEPROM_93C46::ADDRESS_BITS;
82 pattern |= 0b0000000; // 0b00xxxxx
83 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
84 input_pattern(eeprom, time, pattern, len);
85
86 eeprom.write_CS(false, time);
87 time += step;
88}
89
90static bool waitIdle(EEPROM_93C46& eeprom, EmuTime& time)
91{
92 eeprom.write_CS(true, time);
93 time += step;
94
95 bool wasBusy = false;
96 int i = 0;
97 while (i < 10'000) {
98 bool ready = eeprom.read_DO(time);
99 time += step;
100 if (ready) break;
101 wasBusy = true;
102 }
103 CHECK(i < 10'000); // we must not wait indefinitely
104
105 eeprom.write_CS(false, time);
106 time += step;
107
108 return wasBusy;
109}
110
111static uint8_t read(EEPROM_93C46& eeprom, EmuTime& time, unsigned addr)
112{
113 assert(addr < EEPROM_93C46::NUM_ADDRESSES);
114
115 eeprom.write_CS(true, time);
116 time += step;
117
118 uint32_t pattern = 0b110; // start-bit + write-opcode
119 pattern <<= EEPROM_93C46::ADDRESS_BITS;
120 pattern |= addr;
121 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
122 input_pattern(eeprom, time, pattern, len);
123
124 CHECK(eeprom.read_DO(time) == false); // initial 0-bit
125 uint8_t result = 0;
126 for (auto i : xrange(EEPROM_93C46::DATA_BITS)) {
127 (void)i;
128 eeprom.write_CLK(true, time);
129 time += step;
130 result <<= 1;
131 result |= eeprom.read_DO(time);
132 time += step;
133 eeprom.write_CLK(false, time);
134 time += step;
135 }
136
137 eeprom.write_CS(false, time);
138 time += step;
139
140 return result;
141}
142
143static void read_block(EEPROM_93C46& eeprom, EmuTime& time, unsigned addr,
144 unsigned num, uint8_t* output)
145{
146 assert(addr < EEPROM_93C46::NUM_ADDRESSES);
147
148 eeprom.write_CS(true, time);
149 time += step;
150
151 uint32_t pattern = 0b110; // start-bit + write-opcode
152 pattern <<= EEPROM_93C46::ADDRESS_BITS;
153 pattern |= addr;
154 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
155 input_pattern(eeprom, time, pattern, len);
156
157 CHECK(eeprom.read_DO(time) == false); // initial 0-bit
158 for (auto j : xrange(num)) {
159 (void)j;
160 *output = 0;
161 for (auto i : xrange(EEPROM_93C46::DATA_BITS)) {
162 (void)i;
163 eeprom.write_CLK(true, time);
164 time += step;
165 *output <<= 1;
166 *output |= eeprom.read_DO(time);
167 time += step;
168 eeprom.write_CLK(false, time);
169 time += step;
170 }
171 ++output;
172 }
173
174 eeprom.write_CS(false, time);
175 time += step;
176}
177
178static void erase(EEPROM_93C46& eeprom, EmuTime& time, unsigned addr)
179{
180 assert(addr < EEPROM_93C46::NUM_ADDRESSES);
181
182 eeprom.write_CS(true, time);
183 time += step;
184
185 uint32_t pattern = 0b111; // start-bit + '00'-opcode
186 pattern <<= EEPROM_93C46::ADDRESS_BITS;
187 pattern |= addr;
188 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
189 input_pattern(eeprom, time, pattern, len);
190
191 eeprom.write_CS(false, time);
192 time += step;
193}
194
195static void erase_all(EEPROM_93C46& eeprom, EmuTime& time)
196{
197 eeprom.write_CS(true, time);
198 time += step;
199
200 uint32_t pattern = 0b100; // start-bit + '00'-opcode
201 pattern <<= EEPROM_93C46::ADDRESS_BITS;
202 pattern |= 0b1000000; // 0b10xxxxx
203 unsigned len = 3 + EEPROM_93C46::ADDRESS_BITS;
204 input_pattern(eeprom, time, pattern, len);
205
206 eeprom.write_CS(false, time);
207 time += step;
208}
209
210TEST_CASE("EEPROM_93C46")
211{
212 static const XMLElement* xml = [] {
213 auto& doc = XMLDocument::getStaticDocument();
214 return doc.allocateElement("dummy");
215 }();
216 EEPROM_93C46 eeprom(*xml);
217 const uint8_t* data = eeprom.backdoor();
218 EmuTime time = EmuTime::zero();
219
220 // initially filled with 255
221 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
222 CHECK(data[addr] == 255);
223 }
224
225 // write 123 to address 45 .. but eeprom is still write-protected
226 write(eeprom, time, 45, 123);
227 CHECK(!waitIdle(eeprom, time));
228 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
229 CHECK(data[addr] == 255);
230 }
231
232 // again, but first write-enable
233 write_enable(eeprom, time);
234 CHECK(!waitIdle(eeprom, time));
235 write(eeprom, time, 45, 123);
236 CHECK(waitIdle(eeprom, time));
237 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
238 uint8_t expected = (addr == 45) ? 123 : 255;
239 CHECK(data[addr] == expected);
240 }
241
242 // read address 45
243 CHECK(int(read(eeprom, time, 45)) == 123); // contains 123
244 CHECK(!waitIdle(eeprom, time)); // not busy after read
245 CHECK(int(read(eeprom, time, 46)) == 255); // other addr still contains 255
246 CHECK(!waitIdle(eeprom, time)); // not busy after read
247
248 // write to address 45 without first erasing it
249 // You might expect to read the value 123 & 20 == 16, but the M93C46
250 // datasheet documents that the write command does an auto-erase.
251 write(eeprom, time, 45, 20);
252 CHECK(waitIdle(eeprom, time));
253 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
254 uint8_t expected = (addr == 45) ? 20 : 255;
255 CHECK(data[addr] == expected);
256 }
257
258 // erase addr 99 -> doesn't influence addr 45
259 erase(eeprom, time, 99);
260 CHECK(waitIdle(eeprom, time));
261 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
262 uint8_t expected = (addr == 45) ? 20 : 255;
263 CHECK(data[addr] == expected);
264 }
265
266 // erase addr 45 -> all 255 again
267 erase(eeprom, time, 45);
268 CHECK(waitIdle(eeprom, time));
269 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
270 CHECK(data[addr] == 255);
271 }
272
273 // write all
274 write_all(eeprom, time, 77);
275 CHECK(waitIdle(eeprom, time));
276 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
277 CHECK(data[addr] == 77);
278 }
279
280 // write to the end and the start of the address space
281 write(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 2, 5);
282 CHECK(waitIdle(eeprom, time));
283 write(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 1, 11);
284 CHECK(waitIdle(eeprom, time));
285 write(eeprom, time, 0, 22);
286 CHECK(waitIdle(eeprom, time));
287
288 // write-disable and one more write
289 write_disable(eeprom, time);
290 CHECK(!waitIdle(eeprom, time));
291 write(eeprom, time, 1, 33); // not executed
292 CHECK(!waitIdle(eeprom, time));
293 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
294 uint8_t expected = (addr == 126) ? 5
295 : (addr == 127) ? 11
296 : (addr == 0) ? 22
297 : 77;
298 CHECK(data[addr] == expected);
299 }
300
301 // read-block, with wrap-around
302 uint8_t buf[6];
303 read_block(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 3, 6, buf);
304 CHECK(!waitIdle(eeprom, time));
305 CHECK(buf[0] == 77);
306 CHECK(buf[1] == 5);
307 CHECK(buf[2] == 11);
308 CHECK(buf[3] == 22); // wrapped to address 0
309 CHECK(buf[4] == 77); // was write protected
310 CHECK(buf[5] == 77);
311
312 // erase-all, but write-protected
313 erase_all(eeprom, time);
314 CHECK(!waitIdle(eeprom, time));
315 CHECK(data[0] == 22); // not changed
316
317 // erase-all, write-enabled
318 write_enable(eeprom, time);
319 CHECK(!waitIdle(eeprom, time));
320 erase_all(eeprom, time);
321 CHECK(waitIdle(eeprom, time));
322 for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
323 CHECK(data[addr] == 255);
324 }
325}
const uint8_t * backdoor() const
Definition: EEPROM_93C46.hh:37
void write_CS(bool value, EmuTime::param time)
Definition: EEPROM_93C46.cc:76
void write_CLK(bool value, EmuTime::param time)
Definition: EEPROM_93C46.cc:91
bool read_DO(EmuTime::param time) const
Definition: EEPROM_93C46.cc:65
void write_DI(bool value, EmuTime::param time)
TEST_CASE("EEPROM_93C46")
Definition: eeprom.cc:210
CHECK(m3==m3)
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr auto xrange(T e)
Definition: xrange.hh:133