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 
7 using namespace openmsx;
8 
9 constexpr auto step = EmuDuration::usec(10);
10 
11 static 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 
24 static 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;
37  input_pattern(eeprom, time, pattern, len);
38 
39  eeprom.write_CS(false, time);
40  time += step;
41 }
42 
43 static 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;
54  input_pattern(eeprom, time, pattern, len);
55 
56  eeprom.write_CS(false, time);
57  time += step;
58 }
59 
60 static 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 
75 static 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 
90 static 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 
111 static 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 
143 static 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 
178 static 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 
195 static 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 
210 TEST_CASE("EEPROM_93C46")
211 {
212  XMLElement xml;
213  EEPROM_93C46 eeprom(xml);
214  const uint8_t* data = eeprom.backdoor();
215  EmuTime time = EmuTime::zero();
216 
217  // initially filled with 255
218  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
219  CHECK(data[addr] == 255);
220  }
221 
222  // write 123 to address 45 .. but eeprom is still write-protected
223  write(eeprom, time, 45, 123);
224  CHECK(!waitIdle(eeprom, time));
225  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
226  CHECK(data[addr] == 255);
227  }
228 
229  // again, but first write-enable
230  write_enable(eeprom, time);
231  CHECK(!waitIdle(eeprom, time));
232  write(eeprom, time, 45, 123);
233  CHECK(waitIdle(eeprom, time));
234  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
235  uint8_t expected = (addr == 45) ? 123 : 255;
236  CHECK(data[addr] == expected);
237  }
238 
239  // read address 45
240  CHECK(int(read(eeprom, time, 45)) == 123); // contains 123
241  CHECK(!waitIdle(eeprom, time)); // not busy after read
242  CHECK(int(read(eeprom, time, 46)) == 255); // other addr still contains 255
243  CHECK(!waitIdle(eeprom, time)); // not busy after read
244 
245  // write to address 45 without first erasing it
246  // You might expect to read the value 123 & 20 == 16, but the M93C46
247  // datasheet documents that the write command does an auto-erase.
248  write(eeprom, time, 45, 20);
249  CHECK(waitIdle(eeprom, time));
250  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
251  uint8_t expected = (addr == 45) ? 20 : 255;
252  CHECK(data[addr] == expected);
253  }
254 
255  // erase addr 99 -> doesn't influence addr 45
256  erase(eeprom, time, 99);
257  CHECK(waitIdle(eeprom, time));
258  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
259  uint8_t expected = (addr == 45) ? 20 : 255;
260  CHECK(data[addr] == expected);
261  }
262 
263  // erase addr 45 -> all 255 again
264  erase(eeprom, time, 45);
265  CHECK(waitIdle(eeprom, time));
266  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
267  CHECK(data[addr] == 255);
268  }
269 
270  // write all
271  write_all(eeprom, time, 77);
272  CHECK(waitIdle(eeprom, time));
273  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
274  CHECK(data[addr] == 77);
275  }
276 
277  // write to the end and the start of the address space
278  write(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 2, 5);
279  CHECK(waitIdle(eeprom, time));
280  write(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 1, 11);
281  CHECK(waitIdle(eeprom, time));
282  write(eeprom, time, 0, 22);
283  CHECK(waitIdle(eeprom, time));
284 
285  // write-disable and one more write
286  write_disable(eeprom, time);
287  CHECK(!waitIdle(eeprom, time));
288  write(eeprom, time, 1, 33); // not executed
289  CHECK(!waitIdle(eeprom, time));
290  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
291  uint8_t expected = (addr == 126) ? 5
292  : (addr == 127) ? 11
293  : (addr == 0) ? 22
294  : 77;
295  CHECK(data[addr] == expected);
296  }
297 
298  // read-block, with wrap-around
299  uint8_t buf[6];
300  read_block(eeprom, time, EEPROM_93C46::NUM_ADDRESSES - 3, 6, buf);
301  CHECK(!waitIdle(eeprom, time));
302  CHECK(buf[0] == 77);
303  CHECK(buf[1] == 5);
304  CHECK(buf[2] == 11);
305  CHECK(buf[3] == 22); // wrapped to address 0
306  CHECK(buf[4] == 77); // was write protected
307  CHECK(buf[5] == 77);
308 
309  // erase-all, but write-protected
310  erase_all(eeprom, time);
311  CHECK(!waitIdle(eeprom, time));
312  CHECK(data[0] == 22); // not changed
313 
314  // erase-all, write-enabled
315  write_enable(eeprom, time);
316  CHECK(!waitIdle(eeprom, time));
317  erase_all(eeprom, time);
318  CHECK(waitIdle(eeprom, time));
319  for (auto addr : xrange(EEPROM_93C46::NUM_ADDRESSES)) {
320  CHECK(data[addr] == 255);
321  }
322 }
openmsx::EEPROM_93C46::read_DO
bool read_DO(EmuTime::param time) const
Definition: EEPROM_93C46.cc:65
xrange
auto xrange(T e)
Definition: xrange.hh:170
openmsx::EEPROM_93C46::write_DI
void write_DI(bool value, EmuTime::param time)
Definition: EEPROM_93C46.cc:101
openmsx::EEPROM_93C46
Definition: EEPROM_93C46.hh:14
CHECK
CHECK(m3==m3)
XMLElement.hh
TEST_CASE
TEST_CASE("EEPROM_93C46")
Definition: eeprom.cc:210
step
constexpr auto step
Definition: eeprom.cc:9
openmsx::EEPROM_93C46::ADDRESS_BITS
static constexpr uint8_t ADDRESS_BITS
Definition: EEPROM_93C46.hh:17
openmsx::XMLElement
Definition: XMLElement.hh:15
openmsx::EEPROM_93C46::backdoor
const uint8_t * backdoor() const
Definition: EEPROM_93C46.hh:37
EmuTime.hh
openmsx::EEPROM_93C46::write_CS
void write_CS(bool value, EmuTime::param time)
Definition: EEPROM_93C46.cc:76
openmsx::EEPROM_93C46::DATA_BITS
static constexpr uint8_t DATA_BITS
Definition: EEPROM_93C46.hh:20
openmsx::EEPROM_93C46::write_CLK
void write_CLK(bool value, EmuTime::param time)
Definition: EEPROM_93C46.cc:91
EEPROM_93C46.hh
openmsx::EmuDuration::usec
static constexpr EmuDuration usec(unsigned x)
Definition: EmuDuration.hh:43
openmsx::EEPROM_93C46::NUM_ADDRESSES
static constexpr uint32_t NUM_ADDRESSES
Definition: EEPROM_93C46.hh:18
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
xrange.hh