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 | |
86 | <Press Ctrl-e> | |
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'. | |
115 | ||
116 | Simulation stopped, PC: 001000 (BR 1000) | |
117 | sim> quit | |
118 | Goodbye | |
119 | ||
120 | Since this program is an infinite loop, we pressed `Ctrl-e` to pause the | |
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 | |
127 | need to bother with disk images; instead we will load a binary directly into | |
128 | the PDP-11's memory using SIMH's `load` command. | |
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 | |
164 | installation instructions. | |
165 | ||
166 | ||
167 | ## Load and Execute ## | |
168 | ||
169 | We can extract a binary from the a.out file generated by our cross compiler | |
170 | using the `pdp11-aout-objcopy` tool built at the same time as the cross | |
171 | compiler. This binary can then be converted by `bin2load` and loaded into SIMH. | |
172 | ||
173 | % pdp11-aout-objcopy --only-section=.text --output-target binary program.out program.bin | |
174 | % bin2load -i program.bin -o program.pdp11 -a 01000 | |
175 | % pdp11 | |
176 | ||
177 | PDP-11 simulator V3.9-0 | |
178 | sim> load program.pdp11 | |
179 | sim> go | |
180 | ||
181 | If we pass a starting address to `bin2load` with the `-a` flag (as shown | |
182 | above), then SIMH configures the simulation so that execution starts at address | |
183 | `01000` when the `go` command is entered. | |
184 | ||
185 | The `load <file>` and `go` command may also be included in the SIMH | |
186 | configuration file, automatically loading and executing whenever the simulator | |
187 | is started. | |
188 | ||
189 | ||
190 | # Execution # | |
191 | ||
192 | Let's bring all these steps together. Assume we've built our cross compiler and | |
193 | created a `Hello, World!` program like the one shown in the four files below. | |
194 | ||
195 | **`program.c`:** | |
196 | ||
197 | #include <stdint.h> | |
198 | ||
199 | #define XCSR (*((volatile uint16_t *)0177564)) | |
200 | #define XBUF (*((volatile uint16_t *)0177566)) | |
201 | ||
202 | void | |
203 | putch(uint16_t c) | |
204 | { | |
205 | while((XCSR && 0200) == 0) continue; | |
206 | XBUF = c; | |
207 | } | |
208 | ||
209 | void | |
210 | print_string(const char * string) | |
211 | { | |
212 | while (*string != '\0') { | |
213 | putch(*string); | |
214 | string++; | |
215 | } | |
216 | } | |
217 | ||
218 | void | |
219 | cstart(void) | |
220 | { | |
221 | volatile uint16_t * test_word = (volatile uint16_t *) 0140000; | |
222 | *test_word = 0123456; | |
223 | ||
224 | print_string("Hello, World!\r\n"); | |
225 | } | |
226 | ||
227 | **`bootstrap.s`:** | |
228 | ||
229 | .globl _start | |
230 | ||
231 | _start: | |
232 | mov $01000,sp | |
233 | jsr pc,_cstart | |
234 | halt | |
235 | ||
236 | **`pdp11.ld`:** | |
237 | ||
238 | OUTPUT_FORMAT("a.out-pdp11") | |
239 | ENTRY(start) | |
240 | phys = 00001000; | |
241 | SECTIONS | |
242 | { | |
243 | .text phys : AT(phys) { | |
244 | code = .; | |
245 | *(.text) | |
246 | *(.rodata) | |
247 | . = ALIGN(0100); | |
248 | } | |
249 | .data : AT(phys + (data - code)) | |
250 | { | |
251 | data = .; | |
252 | *(.data) | |
253 | . = ALIGN(0100); | |
254 | } | |
255 | .bss : AT(phys + (bss - code)) | |
256 | { | |
257 | bss = .; | |
258 | *(.bss) | |
259 | . = ALIGN(0100); | |
260 | } | |
261 | end = .; | |
262 | } | |
263 | ||
264 | **`simh.conf`:** | |
265 | ||
266 | load program.pdp11 | |
267 | go 1000 | |
268 | ||
269 | Note that the program writes the value `0123456` to address `0140000` and then | |
270 | prints the string `Hello, World!` to the console SLU before halting. | |
271 | ||
272 | We can compile and execute this program with the following sequence of | |
273 | commands. Note that after the program halts, we are able to examine address | |
274 | `0140000` and see the value `0123456` written by the program. | |
275 | ||
276 | % pdp11-aout-as -o bootstrap.o bootstrap.s | |
277 | % pdp11-aout-gcc -c -Wall -Wno-unused-function -O0 -ffreestanding \ | |
278 | -fomit-frame-pointer -fno-builtin-alloca -std=c99 -o program.o program.c | |
279 | % pdp11-aout-ld -T pdp11.ld --entry _start bootstrap.o program.o -o program.out | |
280 | % pdp11-aout-objcopy --only-section=.text --output-target binary program.out program.bin | |
281 | % bin2load -i program.bin -o program.pdp11 -a 01000 | |
282 | % pdp11 simh.conf | |
283 | Paper tape will load at address 01000. | |
284 | ||
285 | PDP-11 simulator V3.9-0 | |
286 | Hello, World! | |
287 | HALT instruction, PC: 001012 (BR 1016) | |
288 | sim> examine 0140000 | |
289 | 140000: 123456 | |
290 | sim> quit | |
291 | Goodbye | |
292 | ||
293 | TADA! Now you can test your programs on the simulator as you write them. | |
294 | ||
295 | ||
296 | # Beyond Basics # | |
297 | ||
298 | SIMH includes many features beyond the basics shown in this document. The | |
299 | curious user may enter `help` at the `sim>` prompt for more information. | |
300 | ||
301 | However, before leaving the topic of testing code on simulated PDP-11s, I want | |
302 | to mention two simulated pieces of hardware provided by SIMH that can be of use | |
303 | in this task. | |
304 | ||
305 | ||
306 | ## Line Printer ## | |
307 | ||
308 | The DEC LP11 line printer is emulated by SIMH and its output is saved to a text | |
309 | file during the simulation. To save output as `line_printer.txt`, execute the | |
310 | following command in SIMH. | |
311 | ||
312 | sim> attach lpt line_printer.txt | |
313 | ||
314 | The DEC LP11 is controlled by two registers, the Line Printer Status register | |
315 | (`LPS`) located at `0177514` and the Line Printer Data Buffer register (`LPDB`) | |
316 | at `0177516`. To use the LP11, simply test bit 7 of `LPS` for printer readiness | |
317 | and then write a 7-bit character into the low bits of `LPDB`. | |
318 | ||
319 | For example, you could use something like the following C code to print from | |
320 | the simulation to the printer text file. | |
321 | ||
322 | #define LPS (*((volatile uint16_t *)0177514)) | |
323 | #define LPDB (*((volatile uint16_t *)0177516)) | |
324 | ||
325 | void | |
326 | putch(uint16_t c) | |
327 | { | |
328 | /* Test bit 7 (aka: 0200) of LPS for readiness. */ | |
329 | while((LPS && 0200) == 0) continue; | |
330 | /* Transfer a byte when ready. */ | |
331 | LPDB = c; | |
332 | } | |
333 | ||
334 | This provides an easy method for your PDP-11 program to output data to the host | |
335 | which is automatically saved, all while requiring minimal code be added to the | |
336 | PDP-11's program. | |
337 | ||
338 | ||
339 | ## Serial via Telnet ## | |
340 | ||
341 | The DEC DC11 asynchronous line interface is used between the PDP-11 and a | |
342 | serial asynch line. Via SIMH, these lines can be redirected to TCP ports which | |
343 | are accessible via `telnet`. For example, to enable eight separate lines in | |
344 | SIMH and listen on port `1170`, type the following at the `sim>` prompt or add | |
345 | to your SIMH configuration file. | |
346 | ||
347 | sim> set dci en | |
348 | sim> set dci lines=8 | |
349 | sim> set dci 1170 | |
350 | ||
351 | Now you can `telnet` to port `1170` and SIMH will redirect your connection to | |
352 | the first available line of the simulated DC11. | |
353 | ||
354 | Inside the simulation, interacting with a DC11 is fairly simple. | |
355 | ||
356 | Four registers are associated with each serial line. The first is at addresses | |
357 | `0177400`-`0177406`, the next at `0177410`-`0177416`, etc. Within a block of | |
358 | four words, the registers are assigned as follows. | |
359 | ||
360 | - `1774xx0`: Receiver Status (`RCSR`) | |
361 | - `1774xx2`: Receiver Buffer (`RBUF`) | |
362 | - `1774xx4`: Transmitter Status (`XCSR`) | |
363 | - `1774xx6`: Transmitter Buffer (`XBUF`) | |
364 | ||
365 | Your program should test bit 7 of the `RCSR` and `XCSR` registers to determine | |
366 | when the serial line has received a byte and is ready for it to be read, or the | |
367 | serial line is ready to transmit a byte. When ready, transfer a byte to the | |
368 | lower half of `XBUF` or from the lower half of `RBUF`. | |
369 | ||
370 | The code for these operations should look just like the code given above for the | |
371 | LP11 line printer. | |
372 | ||
373 | For a full description of the programming interface of the DC11, see the | |
374 | [PDP-11 Peripherals Handbook](http://www.bitsavers.org/pdf/dec/pdp11/handbooks/PDP11_PeripheralsHbk_1972.pdf) | |
375 | starting on page 109. | |
376 | ||
377 | Using these simulated serial lines, you can easily interact with your PDP-11 | |
378 | program via TCP ports, either manually using a `telnet` program, or | |
379 | programmatically. This can be a powerful debugging tool. | |
380 |