rev |
line source |
nuclear@3
|
1 /*
|
nuclear@3
|
2 72pin SIMM DRAM tester.
|
nuclear@3
|
3 Copyright (C) 2017 John Tsiombikas <nuclear@member.fsf.org>
|
nuclear@3
|
4
|
nuclear@3
|
5 This program is free software: you can redistribute it and/or modify
|
nuclear@3
|
6 it under the terms of the GNU General Public License as published by
|
nuclear@3
|
7 the Free Software Foundation, either version 3 of the License, or
|
nuclear@3
|
8 (at your option) any later version.
|
nuclear@3
|
9
|
nuclear@3
|
10 This program is distributed in the hope that it will be useful,
|
nuclear@3
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nuclear@3
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nuclear@3
|
13 GNU General Public License for more details.
|
nuclear@3
|
14
|
nuclear@3
|
15 You should have received a copy of the GNU General Public License
|
nuclear@3
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
nuclear@3
|
17 */
|
nuclear@2
|
18 #define F_CPU XTAL
|
nuclear@2
|
19
|
nuclear@0
|
20 #include <stdio.h>
|
nuclear@1
|
21 #include <stdlib.h>
|
nuclear@0
|
22 #include <string.h>
|
nuclear@0
|
23 #include <ctype.h>
|
nuclear@2
|
24 #include <stdint.h>
|
nuclear@0
|
25 #include <avr/io.h>
|
nuclear@0
|
26 #include <avr/interrupt.h>
|
nuclear@2
|
27 #include <util/delay.h>
|
nuclear@0
|
28 #include "serial.h"
|
nuclear@0
|
29
|
nuclear@1
|
30 /* pin assignments:
|
nuclear@1
|
31 * A[0,7] data
|
nuclear@1
|
32 * B[0,7] A0 - A7
|
nuclear@1
|
33 * C[0,3] A8 - A11
|
nuclear@1
|
34 * D7 CAS3
|
nuclear@1
|
35 * D6 RAS2
|
nuclear@1
|
36 * D5 RAS3
|
nuclear@1
|
37 */
|
nuclear@1
|
38
|
nuclear@1
|
39 #define CAS3_BIT 0x80
|
nuclear@1
|
40 #define RAS2_BIT 0x40
|
nuclear@1
|
41 #define RAS3_BIT 0x20
|
nuclear@1
|
42 #define WE_BIT 0x04
|
nuclear@1
|
43
|
nuclear@1
|
44 void proc_cmd(char *cmd);
|
nuclear@1
|
45 void cmd_read(char *buf);
|
nuclear@1
|
46 void cmd_write(char *buf);
|
nuclear@1
|
47 void cmd_setcfg(char *buf);
|
nuclear@2
|
48 void cmd_test(char *buf);
|
nuclear@2
|
49 void cmd_detect(void);
|
nuclear@1
|
50
|
nuclear@1
|
51 void dram_init(void);
|
nuclear@1
|
52 void dram_detect(void);
|
nuclear@2
|
53 int memtest(uint32_t addr);
|
nuclear@1
|
54 void dram_refresh(void);
|
nuclear@2
|
55 void dram_write(uint32_t addr, unsigned char val);
|
nuclear@2
|
56 unsigned char dram_read(uint32_t addr);
|
nuclear@0
|
57
|
nuclear@0
|
58 #define MAX_INPUT_SIZE 128
|
nuclear@0
|
59 static char input[MAX_INPUT_SIZE];
|
nuclear@0
|
60 static unsigned char inp_cidx;
|
nuclear@0
|
61
|
nuclear@1
|
62 /* SIMM access config */
|
nuclear@1
|
63 static int addr_bits;
|
nuclear@2
|
64 static uint32_t addr_mask;
|
nuclear@1
|
65 static int ras_lines = 1;
|
nuclear@1
|
66 static long memsize_kb; /* derived from the above */
|
nuclear@1
|
67
|
nuclear@0
|
68 int main(void)
|
nuclear@0
|
69 {
|
nuclear@1
|
70 dram_init();
|
nuclear@0
|
71 init_serial(38400);
|
nuclear@0
|
72 sei();
|
nuclear@0
|
73
|
nuclear@2
|
74 printf("\n72pin SIMM DRAM tester by John Tsiombikas <nuclear@member.fsf.org>\n");
|
nuclear@1
|
75
|
nuclear@2
|
76 cmd_detect();
|
nuclear@1
|
77
|
nuclear@1
|
78 fputs("> ", stdout);
|
nuclear@0
|
79
|
nuclear@0
|
80 for(;;) {
|
nuclear@2
|
81 if(have_input()) {
|
nuclear@0
|
82 int c = getchar();
|
nuclear@0
|
83 putchar(c);
|
nuclear@0
|
84
|
nuclear@0
|
85 if(c == '\r' || c == '\n') {
|
nuclear@0
|
86 input[inp_cidx] = 0;
|
nuclear@1
|
87 proc_cmd(input);
|
nuclear@0
|
88 inp_cidx = 0;
|
nuclear@1
|
89
|
nuclear@1
|
90 fputs("> ", stdout);
|
nuclear@0
|
91 } else if(inp_cidx < sizeof input - 1) {
|
nuclear@0
|
92 input[inp_cidx++] = c;
|
nuclear@0
|
93 }
|
nuclear@0
|
94 }
|
nuclear@1
|
95
|
nuclear@1
|
96 dram_refresh();
|
nuclear@1
|
97 }
|
nuclear@1
|
98 return 0;
|
nuclear@1
|
99 }
|
nuclear@1
|
100
|
nuclear@1
|
101 void proc_cmd(char *cmd)
|
nuclear@1
|
102 {
|
nuclear@1
|
103 switch(cmd[0]) {
|
nuclear@1
|
104 case 'w':
|
nuclear@1
|
105 cmd_write(cmd + 1);
|
nuclear@1
|
106 break;
|
nuclear@1
|
107
|
nuclear@1
|
108 case 'r':
|
nuclear@1
|
109 cmd_read(cmd + 1);
|
nuclear@1
|
110 break;
|
nuclear@1
|
111
|
nuclear@1
|
112 case 's':
|
nuclear@1
|
113 cmd_setcfg(cmd + 1);
|
nuclear@1
|
114 break;
|
nuclear@1
|
115
|
nuclear@2
|
116 case 't':
|
nuclear@2
|
117 cmd_test(cmd + 1);
|
nuclear@2
|
118 break;
|
nuclear@2
|
119
|
nuclear@2
|
120 case 'd':
|
nuclear@2
|
121 cmd_detect();
|
nuclear@2
|
122 break;
|
nuclear@2
|
123
|
nuclear@1
|
124 case 'h':
|
nuclear@1
|
125 printf("commands:\n");
|
nuclear@1
|
126 printf(" w <addr> <value> - write byte to address\n");
|
nuclear@1
|
127 printf(" r <addr> - read byte from address\n");
|
nuclear@1
|
128 printf(" s <addr_bits|ras_lines> <value>\n");
|
nuclear@2
|
129 printf(" t <addr> - test address\n");
|
nuclear@2
|
130 printf(" d - detect DRAM\n");
|
nuclear@1
|
131 printf(" h - help\n");
|
nuclear@1
|
132 break;
|
nuclear@1
|
133 }
|
nuclear@1
|
134 }
|
nuclear@1
|
135
|
nuclear@1
|
136 void cmd_read(char *buf)
|
nuclear@1
|
137 {
|
nuclear@1
|
138 char *endp;
|
nuclear@2
|
139 uint32_t addr;
|
nuclear@1
|
140 unsigned char data;
|
nuclear@1
|
141
|
nuclear@1
|
142 addr = strtol(buf, &endp, 0);
|
nuclear@1
|
143 if(endp == buf) {
|
nuclear@1
|
144 fprintf(stderr, "invalid argument to write command: %s\n", buf);
|
nuclear@1
|
145 return;
|
nuclear@1
|
146 }
|
nuclear@1
|
147 buf = endp;
|
nuclear@1
|
148
|
nuclear@1
|
149 data = dram_read(addr);
|
nuclear@2
|
150 printf("%04lx: %02x (%u)\n", (unsigned long)addr, (unsigned int)data, (unsigned int)data);
|
nuclear@1
|
151 }
|
nuclear@1
|
152
|
nuclear@1
|
153 void cmd_write(char *buf)
|
nuclear@1
|
154 {
|
nuclear@1
|
155 char *endp;
|
nuclear@2
|
156 uint32_t addr;
|
nuclear@1
|
157 unsigned char data;
|
nuclear@1
|
158
|
nuclear@1
|
159 addr = strtol(buf, &endp, 0);
|
nuclear@1
|
160 if(endp == buf) {
|
nuclear@1
|
161 fprintf(stderr, "invalid address argument to read command: %s\n", buf);
|
nuclear@1
|
162 return;
|
nuclear@1
|
163 }
|
nuclear@1
|
164 buf = endp;
|
nuclear@1
|
165
|
nuclear@1
|
166 data = strtol(buf, &endp, 0);
|
nuclear@1
|
167 if(endp == buf) {
|
nuclear@1
|
168 fprintf(stderr, "invalid data argument to read command: %s\n", buf);
|
nuclear@1
|
169 return;
|
nuclear@1
|
170 }
|
nuclear@1
|
171 buf = endp;
|
nuclear@1
|
172
|
nuclear@1
|
173 dram_write(addr, data);
|
nuclear@2
|
174 printf("%04lx: %02x (%u)\n", (unsigned long)addr, (unsigned int)data, (unsigned int)data);
|
nuclear@1
|
175 }
|
nuclear@1
|
176
|
nuclear@1
|
177 void cmd_setcfg(char *buf)
|
nuclear@1
|
178 {
|
nuclear@1
|
179 char *endp;
|
nuclear@1
|
180 char *name, *valstr;
|
nuclear@1
|
181 long value;
|
nuclear@1
|
182
|
nuclear@1
|
183 name = buf;
|
nuclear@1
|
184 while(*name && isspace(*name)) ++name;
|
nuclear@1
|
185 if(!*name) {
|
nuclear@1
|
186 fprintf(stderr, "invalid or missing variable name\n");
|
nuclear@1
|
187 return;
|
nuclear@1
|
188 }
|
nuclear@1
|
189 endp = name;
|
nuclear@1
|
190 while(*endp && !isspace(*endp)) ++endp;
|
nuclear@1
|
191 *endp = 0;
|
nuclear@1
|
192
|
nuclear@1
|
193 valstr = endp + 1;
|
nuclear@1
|
194 value = strtol(valstr, &endp, 0);
|
nuclear@1
|
195 if(endp == valstr) {
|
nuclear@1
|
196 fprintf(stderr, "invalid or missing variable value: %s\n", valstr);
|
nuclear@1
|
197 return;
|
nuclear@1
|
198 }
|
nuclear@1
|
199
|
nuclear@1
|
200 if(strcmp(name, "addr_bits") == 0) {
|
nuclear@1
|
201 if(value > 0 && value <= 12) {
|
nuclear@1
|
202 addr_bits = value;
|
nuclear@2
|
203 addr_mask = ((uint32_t)1 << addr_bits) - 1;
|
nuclear@2
|
204 printf("Address bits: %ld (mask: %lx)\n", value, (unsigned long)addr_mask);
|
nuclear@1
|
205 } else {
|
nuclear@1
|
206 fprintf(stderr, "invalid address bits value: %ld\n", value);
|
nuclear@1
|
207 }
|
nuclear@1
|
208 } else if(strcmp(name, "ras_lines") == 0) {
|
nuclear@1
|
209 if(value > 0 && value <= 2) {
|
nuclear@1
|
210 ras_lines = value;
|
nuclear@1
|
211 printf("RAS lines: %d\n", ras_lines);
|
nuclear@1
|
212 } else {
|
nuclear@1
|
213 fprintf(stderr, "invalid RAS lines value: %ld\n", value);
|
nuclear@1
|
214 }
|
nuclear@1
|
215 } else {
|
nuclear@1
|
216 fprintf(stderr, "unknown variable: %s\n", name);
|
nuclear@1
|
217 }
|
nuclear@1
|
218 }
|
nuclear@1
|
219
|
nuclear@2
|
220 void cmd_test(char *buf)
|
nuclear@2
|
221 {
|
nuclear@2
|
222 char *endp;
|
nuclear@2
|
223 uint32_t addr;
|
nuclear@2
|
224
|
nuclear@2
|
225 addr = strtol(buf, &endp, 0);
|
nuclear@2
|
226 if(endp == buf) {
|
nuclear@2
|
227 fprintf(stderr, "invalid argument to test command: %s\n", buf);
|
nuclear@2
|
228 return;
|
nuclear@2
|
229 }
|
nuclear@2
|
230
|
nuclear@2
|
231 if(memtest(addr) == 0) {
|
nuclear@2
|
232 printf("success!\n");
|
nuclear@2
|
233 }
|
nuclear@2
|
234 }
|
nuclear@2
|
235
|
nuclear@2
|
236
|
nuclear@2
|
237 void cmd_detect(void)
|
nuclear@2
|
238 {
|
nuclear@2
|
239 printf("Detecting memory ...\n");
|
nuclear@2
|
240 dram_detect();
|
nuclear@2
|
241
|
nuclear@2
|
242 memsize_kb = ((uint32_t)1 << (addr_bits * 2)) * 4 * ras_lines;
|
nuclear@2
|
243
|
nuclear@2
|
244 printf("Address lines: %d\n", addr_bits);
|
nuclear@2
|
245 printf("RAS lines: %d\n", ras_lines);
|
nuclear@2
|
246 printf("Memory size: %ldmb (%ldkb)\n", memsize_kb >> 20, memsize_kb >> 10);
|
nuclear@2
|
247 }
|
nuclear@2
|
248
|
nuclear@2
|
249
|
nuclear@1
|
250 void dram_set_data(unsigned char val)
|
nuclear@1
|
251 {
|
nuclear@1
|
252 DDRA = 0xff;
|
nuclear@1
|
253 PORTA = val;
|
nuclear@1
|
254 }
|
nuclear@1
|
255
|
nuclear@1
|
256 void dram_release_data(void)
|
nuclear@1
|
257 {
|
nuclear@1
|
258 DDRA = 0;
|
nuclear@1
|
259 PORTA = 0;
|
nuclear@1
|
260 }
|
nuclear@1
|
261
|
nuclear@1
|
262 void dram_set_addr(unsigned long addr)
|
nuclear@1
|
263 {
|
nuclear@1
|
264 PORTB = addr & 0xff;
|
nuclear@1
|
265 PORTC = (addr >> 8) & 3;
|
nuclear@1
|
266 }
|
nuclear@1
|
267
|
nuclear@1
|
268 void dram_assert_cas(void)
|
nuclear@1
|
269 {
|
nuclear@1
|
270 PORTD &= ~CAS3_BIT;
|
nuclear@1
|
271 }
|
nuclear@1
|
272
|
nuclear@1
|
273 void dram_release_cas(void)
|
nuclear@1
|
274 {
|
nuclear@1
|
275 PORTD |= CAS3_BIT;
|
nuclear@1
|
276 }
|
nuclear@1
|
277
|
nuclear@1
|
278 void dram_assert_ras(unsigned char bits)
|
nuclear@1
|
279 {
|
nuclear@1
|
280 PORTD &= ~bits;
|
nuclear@1
|
281 }
|
nuclear@1
|
282
|
nuclear@1
|
283 void dram_release_ras(unsigned char bits)
|
nuclear@1
|
284 {
|
nuclear@1
|
285 PORTD |= bits;
|
nuclear@1
|
286 }
|
nuclear@1
|
287
|
nuclear@1
|
288 void dram_set_we(void)
|
nuclear@1
|
289 {
|
nuclear@1
|
290 PORTD &= ~WE_BIT;
|
nuclear@1
|
291 }
|
nuclear@1
|
292
|
nuclear@1
|
293 void dram_clear_we(void)
|
nuclear@1
|
294 {
|
nuclear@1
|
295 PORTD |= WE_BIT;
|
nuclear@1
|
296 }
|
nuclear@1
|
297
|
nuclear@1
|
298 void dram_init(void)
|
nuclear@1
|
299 {
|
nuclear@1
|
300 DDRA = 0; /* port A is the data bus */
|
nuclear@1
|
301 PORTA = 0; /* no pullups when A is input */
|
nuclear@1
|
302 DDRB = 0xff; /* port B is A0-A7 */
|
nuclear@1
|
303 DDRC = 0xff; /* port C (low nibble) is A8-A11 */
|
nuclear@1
|
304 DDRD = 0xff; /* port D are the control lines CAS/RAS/WR */
|
nuclear@1
|
305
|
nuclear@1
|
306 PORTD = 0xff; /* deassert all control signals */
|
nuclear@2
|
307
|
nuclear@2
|
308 /* it seems like nothing works until we do one refresh cycle... */
|
nuclear@2
|
309 dram_refresh();
|
nuclear@1
|
310 }
|
nuclear@1
|
311
|
nuclear@1
|
312 void dram_detect(void)
|
nuclear@1
|
313 {
|
nuclear@2
|
314 uint32_t addr = 0;
|
nuclear@2
|
315
|
nuclear@1
|
316 /* detect how many address bits we've got */
|
nuclear@1
|
317 addr_bits = 12;
|
nuclear@1
|
318 while(addr_bits > 8) {
|
nuclear@2
|
319 addr_mask = ((uint32_t)1 << (uint32_t)addr_bits) - 1;
|
nuclear@2
|
320 addr = ((uint32_t)1 << ((uint32_t)addr_bits * 2)) - 1;
|
nuclear@2
|
321 if(memtest(addr) == 0) {
|
nuclear@1
|
322 break;
|
nuclear@1
|
323 }
|
nuclear@1
|
324 --addr_bits;
|
nuclear@1
|
325 }
|
nuclear@1
|
326 if(addr_bits < 1) {
|
nuclear@1
|
327 fprintf(stderr, "Failed to detect DRAM configuration (address lines)...\n");
|
nuclear@1
|
328 return;
|
nuclear@1
|
329 }
|
nuclear@2
|
330
|
nuclear@2
|
331 /* now detect if there's a second ras pair */
|
nuclear@2
|
332 ++addr; /* addr was already the highest of the first bank, see if there's a second */
|
nuclear@2
|
333 ras_lines = 2;
|
nuclear@2
|
334 if(memtest(addr) != 0) {
|
nuclear@2
|
335 ras_lines = 1;
|
nuclear@2
|
336 }
|
nuclear@1
|
337 }
|
nuclear@1
|
338
|
nuclear@2
|
339 int memtest(uint32_t addr)
|
nuclear@1
|
340 {
|
nuclear@1
|
341 int i;
|
nuclear@1
|
342 unsigned char pat[] = { 0xf0, 0x0f, 0xaa, 0x55, 0xc0, 0x30, 0x0c, 0x03 };
|
nuclear@1
|
343 unsigned char val;
|
nuclear@1
|
344
|
nuclear@2
|
345 printf("testing address: %lx (a:%d,r:%d)\n", (unsigned long)addr, addr_bits, ras_lines);
|
nuclear@1
|
346
|
nuclear@1
|
347 for(i=0; i<sizeof pat / sizeof *pat; i++) {
|
nuclear@1
|
348 dram_write(addr, pat[i]);
|
nuclear@1
|
349 if((val = dram_read(addr)) != pat[i]) {
|
nuclear@2
|
350 printf("pattern %x failed, got: %x\n", (unsigned int)pat[i], (unsigned int)val);
|
nuclear@1
|
351 return -1;
|
nuclear@1
|
352 }
|
nuclear@0
|
353 }
|
nuclear@0
|
354 return 0;
|
nuclear@0
|
355 }
|
nuclear@0
|
356
|
nuclear@1
|
357 void dram_refresh(void)
|
nuclear@0
|
358 {
|
nuclear@1
|
359 dram_assert_cas();
|
nuclear@1
|
360 dram_assert_ras(RAS2_BIT | RAS3_BIT);
|
nuclear@1
|
361 dram_release_cas();
|
nuclear@1
|
362 dram_release_ras(RAS2_BIT | RAS3_BIT);
|
nuclear@0
|
363 }
|
nuclear@1
|
364
|
nuclear@2
|
365 void dram_write(uint32_t addr, unsigned char val)
|
nuclear@1
|
366 {
|
nuclear@2
|
367 uint32_t row_addr = (addr >> addr_bits) & addr_mask;
|
nuclear@2
|
368 uint32_t col_addr = addr & addr_mask;
|
nuclear@2
|
369 unsigned char ras = (addr >> (addr_bits * 2)) ? RAS3_BIT : RAS2_BIT;
|
nuclear@2
|
370
|
nuclear@1
|
371 dram_set_data(val);
|
nuclear@1
|
372 dram_set_we();
|
nuclear@1
|
373 /* set row address */
|
nuclear@2
|
374 dram_set_addr(row_addr);
|
nuclear@2
|
375 dram_assert_ras(ras);
|
nuclear@1
|
376 /* set column address */
|
nuclear@2
|
377 dram_set_addr(col_addr);
|
nuclear@1
|
378 dram_assert_cas();
|
nuclear@2
|
379 dram_release_ras(ras);
|
nuclear@1
|
380 dram_release_cas();
|
nuclear@1
|
381 dram_release_data();
|
nuclear@1
|
382 dram_clear_we();
|
nuclear@1
|
383 }
|
nuclear@1
|
384
|
nuclear@2
|
385 unsigned char dram_read(uint32_t addr)
|
nuclear@1
|
386 {
|
nuclear@1
|
387 unsigned char val;
|
nuclear@2
|
388 uint32_t row_addr = (addr >> addr_bits) & addr_mask;
|
nuclear@2
|
389 uint32_t col_addr = addr & addr_mask;
|
nuclear@2
|
390 unsigned char ras = (addr >> (addr_bits * 2)) ? RAS3_BIT : RAS2_BIT;
|
nuclear@1
|
391
|
nuclear@1
|
392 dram_clear_we();
|
nuclear@2
|
393 /* this is necessary to remove previous data from the lines when no-one is driving them
|
nuclear@2
|
394 * in case we're trying to detect the presence of a RAS line which doesn't exist
|
nuclear@2
|
395 */
|
nuclear@2
|
396 dram_set_data(0);
|
nuclear@1
|
397 dram_release_data();
|
nuclear@1
|
398
|
nuclear@1
|
399 /* set row address */
|
nuclear@2
|
400 dram_set_addr(row_addr);
|
nuclear@2
|
401 dram_assert_ras(ras);
|
nuclear@1
|
402 /* set column address */
|
nuclear@2
|
403 dram_set_addr(col_addr);
|
nuclear@1
|
404 dram_assert_cas();
|
nuclear@1
|
405
|
nuclear@1
|
406 val = PINA;
|
nuclear@1
|
407
|
nuclear@2
|
408 dram_release_ras(ras);
|
nuclear@1
|
409 dram_release_cas();
|
nuclear@1
|
410
|
nuclear@1
|
411 return val;
|
nuclear@1
|
412 }
|