Commit | Line | Data |
---|---|---|
477e344c AT |
1 | # Overview # |
2 | ||
3 | TODO: Write introduction. Goal is to run baremetal PDP-11 programs in SIMH, a | |
4 | PDP-11 simulator. | |
5 | ||
6 | TODO: What kind of joint header do I want across all the articles in a set, | |
7 | linking them together? | |
8 | ||
9 | # SIMH Installation # | |
10 | ||
11 | We first need to install [SIMH](https://github.com/simh/simh), a program that | |
12 | simulates many computer architectures including the DEC PDP-11. | |
13 | ||
14 | - **FreeBSD:** Either install package via `pkg install simh` or build from | |
15 | ports under `emulators/simh`. | |
16 | ||
17 | - **Debian:** Install as package via `sudo apt install simh`. | |
18 | ||
19 | - **Windows:** Download [pre-built snapshot binaries](https://github.com/simh/Win32-Development-Binaries). | |
20 | ||
21 | - **Other:** Read the [SIMH README](https://github.com/simh/simh) section | |
22 | "Building Simulators Yourself". | |
23 | ||
24 | After installation, launch the PDP-11 simulator with the command `pdp11`. It | |
25 | should display a prompt `sim>` where you can type `quit` followed by `ENTER` to | |
26 | exit the simulator. | |
27 | ||
28 | % pdp11 | |
29 | ||
30 | PDP-11 simulator V3.9-0 | |
31 | sim> quit | |
32 | Goodbye | |
33 | % | |
34 | ||
35 | ||
36 | # SIMH Basics # | |
37 | ||
38 | Now that SIMH is installed, go ahead and launch it again with the command | |
39 | `pdp11`. This should result in the prompt `sim>`. All commands entered at this | |
40 | `sim>` prompt are executed by the SIMH simulator itself, not by the simulated | |
41 | PDP-11. For example, we could display the configuration of the simulated | |
42 | PDP-11 defined in SIMH. | |
43 | ||
44 | sim> show configuration | |
45 | PDP-11 simulator configuration | |
46 | ||
47 | CPU, 11/73, NOCIS, idle disabled, autoconfiguration enabled, 256KB SYSTEM | |
48 | [...] | |
49 | ||
50 | We can change the simulated PDP-11, for example by changing to a different | |
51 | model. | |
52 | ||
53 | sim> set cpu 11/40 | |
54 | Disabling XQ | |
55 | sim> show configuration | |
56 | PDP-11 simulator configuration | |
57 | ||
58 | CPU, 11/40, NOFIS, idle disabled, autoconfiguration enabled, 256KB SYSTEM | |
59 | [...] | |
60 | ||
61 | SIMH also allows us to deposit values directly into memory and read them back. | |
62 | Note that addresses in SIMH refer to the physical address rather than the | |
63 | virtual address, and thus may be up to 22-bits long on certain models, as shown | |
64 | below. | |
65 | ||
66 | sim> examine 0140000 | |
67 | 140000: 000000 | |
68 | sim> deposit 0140000 042 | |
69 | sim> examine 0140000 | |
70 | 140000: 000042 | |
71 | sim> | |
72 | ||
73 | Once a program is loaded into memory, the simulated PDP-11 may be commanded to | |
74 | execute it via the command `go <address>`. For example, if the program was | |
75 | loaded starting at address `01000`, then begin execution with `go 01000`. | |
76 | ||
77 | ||
78 | ## Interrupting Simulation ## | |
79 | ||
80 | At any time, the simulation may be paused by pressing `Ctrl-e`. This returns to | |
81 | the `sim>` prompt where memory may be examined or other extra-sim capabilities | |
82 | executed. For example, if we have the PDP-11 CPU execute a tight infinite loop, | |
83 | the simulation never naturally ends, but we can pause it, allowing us to exit. | |
84 | ||
85 | sim> go | |
e5f02f11 | 86 | <Simulation runs until user presses Ctrl-e.> |
477e344c AT |
87 | Simulation stopped, PC: 004166 (BR 4166) |
88 | sim> quit | |
89 | Goodbye | |
90 | % | |
91 | ||
92 | Note that, instead of `quit`, typing `go` would have resumed the simulation | |
93 | exactly where it paused, shown in the status message where the `PC` register is | |
94 | set to address `04166`. | |
95 | ||
96 | ||
97 | ## Saving Configuration ## | |
98 | ||
99 | Any command that may be entered at the `sim>` prompt, may also be entered in a | |
100 | configuration file. For example, consider the following file named `simh.conf`. | |
101 | ||
102 | deposit 01000 0777 | |
103 | echo Just set address 01000 to the instruction 'BRANCH 01000'. | |
104 | go 01000 | |
105 | ||
106 | We can tell SIMH to load this file and execute each line as though it were | |
107 | typed at the `sim>` prompt by including the config file name. Even though the | |
108 | commands are not displayed, we can see from the `echo` command that they were | |
109 | executed. | |
110 | ||
111 | % pdp11 simh.conf | |
112 | ||
113 | PDP-11 simulator V3.9-0 | |
114 | Just set address 01000 to the instruction 'BRANCH 01000'. | |
e5f02f11 | 115 | <Simulation runs until user presses Ctrl-e.> |
477e344c AT |
116 | Simulation stopped, PC: 001000 (BR 1000) |
117 | sim> quit | |
118 | Goodbye | |
119 | ||
e5f02f11 | 120 | Because this program is an infinite loop, we pressed `Ctrl-e` to pause the |
477e344c AT |
121 | simulation and allow us to quit. |
122 | ||
123 | ||
124 | # SIMH Loader # | |
125 | ||
126 | Since we're trying to run bare-metal code on this simulated PDP-11, we don't | |
e5f02f11 AT |
127 | need to bother with disk images; we will load a binary directly into the |
128 | PDP-11's memory using SIMH's `load` command. | |
477e344c AT |
129 | |
130 | ||
131 | ## Loader Format ## | |
132 | ||
133 | The loader included with SIMH doesn't accept a raw binary file or the a.out | |
134 | executable generated by our cross compiler. Instead, it expects a paper tape | |
135 | image file in the following format. | |
136 | ||
137 | ||
138 | Loader format consists of blocks, optionally preceded, separated, and | |
139 | followed by zeroes. Each block consists of the following entries. Note | |
140 | that all entries are one byte. | |
141 | ||
142 | 0001 | |
143 | 0000 | |
144 | Low byte of block length (data byte count + 6 for header, excludes checksum) | |
145 | High byte of block length | |
146 | Low byte of load address | |
147 | High byte of load address | |
148 | Data byte 0 | |
149 | ... | |
150 | Data byte N | |
151 | Checksum | |
152 | ||
153 | The 8-bit checksum for a block is the twos-complement of the lower eight | |
154 | sum bits for all six header bytes and all data bytes. | |
155 | ||
156 | If the block length is exactly six bytes (i.e. only header, no data), | |
157 | then the block marks the end-of-tape. The checksum should be zero. If | |
158 | the load address of this final block is not 000001, then it is used as | |
159 | the starting PC. | |
160 | ||
161 | If you don't want to generate this format yourself, use the utility | |
162 | [bin2load](https://git.subgeniuskitty.com/pdp11-bin2load/.git) which converts a | |
163 | binary image into a SIMH compatible loader format. See the `README.md` file for | |
e5f02f11 AT |
164 | installation instructions. For example: |
165 | ||
166 | $ git clone git://git.subgeniuskitty.com/pdp11-bin2load | |
167 | $ cd pdp11-bin2load | |
168 | $ make install | |
169 | $ export PATH=$HOME/bin:$PATH | |
170 | $ bin2load -h | |
171 | bin2load v2 (www.subgeniuskitty.com) | |
172 | Usage: bin2load -i <file> -o <file> [-a <address>] | |
173 | -i <file> Raw binary file to be written to tape. | |
174 | For example, output from 'pdp11-aout-objdump' (see README.md). | |
175 | -o <file> Output file created by bin2load containing tape image for use with SIMH. | |
176 | -a <address> (optional) Address on PDP-11 at which to load tape contents. | |
477e344c AT |
177 | |
178 | ||
179 | ## Load and Execute ## | |
180 | ||
181 | We can extract a binary from the a.out file generated by our cross compiler | |
182 | using the `pdp11-aout-objcopy` tool built at the same time as the cross | |
183 | compiler. This binary can then be converted by `bin2load` and loaded into SIMH. | |
184 | ||
185 | % pdp11-aout-objcopy --only-section=.text --output-target binary program.out program.bin | |
186 | % bin2load -i program.bin -o program.pdp11 -a 01000 | |
187 | % pdp11 | |
188 | ||
189 | PDP-11 simulator V3.9-0 | |
190 | sim> load program.pdp11 | |
191 | sim> go | |
192 | ||
193 | If we pass a starting address to `bin2load` with the `-a` flag (as shown | |
e5f02f11 AT |
194 | above), then SIMH configures the simulation to begin execution at the passed |
195 | address. | |
477e344c AT |
196 | |
197 | The `load <file>` and `go` command may also be included in the SIMH | |
198 | configuration file, automatically loading and executing whenever the simulator | |
199 | is started. | |
200 | ||
201 | ||
202 | # Execution # | |
203 | ||
204 | Let's bring all these steps together. Assume we've built our cross compiler and | |
205 | created a `Hello, World!` program like the one shown in the four files below. | |
206 | ||
207 | **`program.c`:** | |
208 | ||
209 | #include <stdint.h> | |
210 | ||
211 | #define XCSR (*((volatile uint16_t *)0177564)) | |
212 | #define XBUF (*((volatile uint16_t *)0177566)) | |
213 | ||
214 | void | |
215 | putch(uint16_t c) | |
216 | { | |
217 | while((XCSR && 0200) == 0) continue; | |
218 | XBUF = c; | |
219 | } | |
220 | ||
221 | void | |
222 | print_string(const char * string) | |
223 | { | |
224 | while (*string != '\0') { | |
225 | putch(*string); | |
226 | string++; | |
227 | } | |
228 | } | |
229 | ||
230 | void | |
231 | cstart(void) | |
232 | { | |
233 | volatile uint16_t * test_word = (volatile uint16_t *) 0140000; | |
234 | *test_word = 0123456; | |
235 | ||
236 | print_string("Hello, World!\r\n"); | |
237 | } | |
238 | ||
239 | **`bootstrap.s`:** | |
240 | ||
241 | .globl _start | |
242 | ||
243 | _start: | |
244 | mov $01000,sp | |
245 | jsr pc,_cstart | |
246 | halt | |
247 | ||
248 | **`pdp11.ld`:** | |
249 | ||
250 | OUTPUT_FORMAT("a.out-pdp11") | |
251 | ENTRY(start) | |
252 | phys = 00001000; | |
253 | SECTIONS | |
254 | { | |
255 | .text phys : AT(phys) { | |
256 | code = .; | |
257 | *(.text) | |
258 | *(.rodata) | |
259 | . = ALIGN(0100); | |
260 | } | |
261 | .data : AT(phys + (data - code)) | |
262 | { | |
263 | data = .; | |
264 | *(.data) | |
265 | . = ALIGN(0100); | |
266 | } | |
267 | .bss : AT(phys + (bss - code)) | |
268 | { | |
269 | bss = .; | |
270 | *(.bss) | |
271 | . = ALIGN(0100); | |
272 | } | |
273 | end = .; | |
274 | } | |
275 | ||
276 | **`simh.conf`:** | |
277 | ||
278 | load program.pdp11 | |
279 | go 1000 | |
280 | ||
281 | Note that the program writes the value `0123456` to address `0140000` and then | |
282 | prints the string `Hello, World!` to the console SLU before halting. | |
283 | ||
284 | We can compile and execute this program with the following sequence of | |
e5f02f11 AT |
285 | commands. After the program halts, we are able to examine address `0140000` and |
286 | see the value `0123456` written by the program. | |
477e344c AT |
287 | |
288 | % pdp11-aout-as -o bootstrap.o bootstrap.s | |
289 | % pdp11-aout-gcc -c -Wall -Wno-unused-function -O0 -ffreestanding \ | |
290 | -fomit-frame-pointer -fno-builtin-alloca -std=c99 -o program.o program.c | |
291 | % pdp11-aout-ld -T pdp11.ld --entry _start bootstrap.o program.o -o program.out | |
292 | % pdp11-aout-objcopy --only-section=.text --output-target binary program.out program.bin | |
293 | % bin2load -i program.bin -o program.pdp11 -a 01000 | |
294 | % pdp11 simh.conf | |
295 | Paper tape will load at address 01000. | |
296 | ||
297 | PDP-11 simulator V3.9-0 | |
298 | Hello, World! | |
299 | HALT instruction, PC: 001012 (BR 1016) | |
300 | sim> examine 0140000 | |
301 | 140000: 123456 | |
302 | sim> quit | |
303 | Goodbye | |
304 | ||
305 | TADA! Now you can test your programs on the simulator as you write them. | |
306 | ||
307 | ||
308 | # Beyond Basics # | |
309 | ||
310 | SIMH includes many features beyond the basics shown in this document. The | |
311 | curious user may enter `help` at the `sim>` prompt for more information. | |
312 | ||
313 | However, before leaving the topic of testing code on simulated PDP-11s, I want | |
314 | to mention two simulated pieces of hardware provided by SIMH that can be of use | |
315 | in this task. | |
316 | ||
317 | ||
318 | ## Line Printer ## | |
319 | ||
e5f02f11 | 320 | The DEC LP11 line printer is simulated by SIMH and its output saved to a text |
477e344c AT |
321 | file during the simulation. To save output as `line_printer.txt`, execute the |
322 | following command in SIMH. | |
323 | ||
324 | sim> attach lpt line_printer.txt | |
325 | ||
326 | The DEC LP11 is controlled by two registers, the Line Printer Status register | |
327 | (`LPS`) located at `0177514` and the Line Printer Data Buffer register (`LPDB`) | |
328 | at `0177516`. To use the LP11, simply test bit 7 of `LPS` for printer readiness | |
329 | and then write a 7-bit character into the low bits of `LPDB`. | |
330 | ||
331 | For example, you could use something like the following C code to print from | |
332 | the simulation to the printer text file. | |
333 | ||
334 | #define LPS (*((volatile uint16_t *)0177514)) | |
335 | #define LPDB (*((volatile uint16_t *)0177516)) | |
336 | ||
337 | void | |
338 | putch(uint16_t c) | |
339 | { | |
340 | /* Test bit 7 (aka: 0200) of LPS for readiness. */ | |
341 | while((LPS && 0200) == 0) continue; | |
342 | /* Transfer a byte when ready. */ | |
343 | LPDB = c; | |
344 | } | |
345 | ||
346 | This provides an easy method for your PDP-11 program to output data to the host | |
347 | which is automatically saved, all while requiring minimal code be added to the | |
348 | PDP-11's program. | |
349 | ||
350 | ||
351 | ## Serial via Telnet ## | |
352 | ||
353 | The DEC DC11 asynchronous line interface is used between the PDP-11 and a | |
354 | serial asynch line. Via SIMH, these lines can be redirected to TCP ports which | |
355 | are accessible via `telnet`. For example, to enable eight separate lines in | |
356 | SIMH and listen on port `1170`, type the following at the `sim>` prompt or add | |
357 | to your SIMH configuration file. | |
358 | ||
359 | sim> set dci en | |
360 | sim> set dci lines=8 | |
361 | sim> set dci 1170 | |
362 | ||
363 | Now you can `telnet` to port `1170` and SIMH will redirect your connection to | |
364 | the first available line of the simulated DC11. | |
365 | ||
366 | Inside the simulation, interacting with a DC11 is fairly simple. | |
367 | ||
368 | Four registers are associated with each serial line. The first is at addresses | |
369 | `0177400`-`0177406`, the next at `0177410`-`0177416`, etc. Within a block of | |
370 | four words, the registers are assigned as follows. | |
371 | ||
372 | - `1774xx0`: Receiver Status (`RCSR`) | |
373 | - `1774xx2`: Receiver Buffer (`RBUF`) | |
374 | - `1774xx4`: Transmitter Status (`XCSR`) | |
375 | - `1774xx6`: Transmitter Buffer (`XBUF`) | |
376 | ||
377 | Your program should test bit 7 of the `RCSR` and `XCSR` registers to determine | |
378 | when the serial line has received a byte and is ready for it to be read, or the | |
379 | serial line is ready to transmit a byte. When ready, transfer a byte to the | |
380 | lower half of `XBUF` or from the lower half of `RBUF`. | |
381 | ||
382 | The code for these operations should look just like the code given above for the | |
383 | LP11 line printer. | |
384 | ||
385 | For a full description of the programming interface of the DC11, see the | |
386 | [PDP-11 Peripherals Handbook](http://www.bitsavers.org/pdf/dec/pdp11/handbooks/PDP11_PeripheralsHbk_1972.pdf) | |
387 | starting on page 109. | |
388 | ||
389 | Using these simulated serial lines, you can easily interact with your PDP-11 | |
390 | program via TCP ports, either manually using a `telnet` program, or | |
391 | programmatically. This can be a powerful debugging tool. | |
392 |