I don't have one at hand. The last one I did, a one-off for testing (contractor provided software had issues, we couldn't test because of that, wrote a subset of its features).
The specification can vary, but you still need a specification. In that case what I ended up with was an org file that detailed the timeline for the program (had 3 states for initialization, then went into execution state), the various tasks (embedded system, roughly comparable to threads or processes, but no parallelism potential, more a logical breakdown of the program structure).
I had a set of messages described as potential inputs, a set of messages described as responses. Another set of messages that I sent periodically (description included when they were to be sent and what their contents were supposed to be).
In this case, like I said before, we didn't have unit tests. It was more equivalent to integration tests. But we used a subset of our test suite to verify that (with this fake-driver in place) the software we were developing was properly sending and receiving inputs. So we were able to reuse existing testing infrastructure. Also, in this case, every test "failed". But the portions of it that we cared about passed (well enough, my messages weren't as dynamic as the real thing so the failures were all expected failures).
The org file was, more or less, like this:
* Driver [/]
** TODO States [/]
- [ ] Initialize - INIT
Configure shared memory so other applications can access it
- [ ] Wait for core - CORE_WAIT
Wait for core to write value X to location Y.
- [ ] Wait for app - APP_WAIT
Wait for app to reach ready state
** TODO CORE_WAIT [/]
- [ ] Some details on what we actually cared about here
** TODO Input Messages [/]
- [ ] Message X
Details on format, later turned into a C struct
- [ ] Message Y
Details on how it should change the driver state
- [ ] Message Z
Details on how we should respond (with Messages A, B, C)
- [[MSG_A]] is used when Z.field = 0
- [[MSG_B]] is used when Z.field = 1
** TODO Output Messages [/]
*** TODO Responses [/]
- [ ] > Message A
Sent in response to Z if Z.field = 0
- [ ] > Message B
Sent in response to Z if Z.field = 1
*** TODO Periodic (not responses) [/]
- [ ] > Message D
That's a specification (with details removed). This is how I generally write things. Start off with an outline, fill it in. Create links to other areas of the specification, resources like message specification (ours is a very domain-specific one, but others might just be RFCs on more standard or common protocols), language or library documentation, with org-mode you can link directly to source code (I don't utilize this as much as I'd like). A side benefit here is that I can tie it in with my task management system. And tracking progress is automatically done (the [/] bits will tally the number of completed tasks and the total, in emacs this is also colored red/green (default) so I can quickly see what's left).
Want a diagram, that's fine. In my office that means a visio document. I'd make that and link to the file as well. I actually did, in this case, link to a few provided diagrams. Perhaps instead of the detailed text on the message format, I link directly to a specification for message formats. I sometimes write one-off programs to test out the provided libraries or test my actual understanding of the hardware we're running on (useful, because it's often not compliant to the specification, still under development). I almost always (I'd say 80%) write my code like this these days (last 2 years in particular, less consistent before that, and before that I was a tester). Any program that's more than 50 lines of (typically) C, ends up getting this treatment.
I recognize that it's somewhat easier in my primary domain (embedded systems). We generally deal with smaller programs (< 30k SLOC of C), usually know exactly what the inputs and outputs ought to be. In other domains you have to be more flexible. But this structure isn't inherently rigid. Providing a specification, even if it's piecemeal, is better than not as it gives you something to measure against.