| 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 | |