This Compucolor 2 emulator uses disk images with the file extension .ccvf. Here is an example file. I would like to understand how to interpret that format.

I understand from the documentation that the disk contains a raw bitstream to be fed to a UART, using one low start bit and one high stop bit. The emulator source code makes it clear that the "bytes" in the .ccvf file are incidental, and they should be interpreted as a sequence of bits.

However, if I do that (and in the same bit order as reverse-engineered from the peekByte function linked above), the resulting 10-bit units don't seem to match the start/8-bit data/stop format that I expected. For example, track 1 of this disk image, when converted to a stream of bits, starts like this (grouped in 10s):


I would expect the middle 8 bits to correspond to the actual data (as bytes), in the bit order prescribed by the UART; but we can see 0 stop bits (e.g. line 1) and 1 start bits (e.g. line 2), which don't make much sense.

My goal as a first step would be to get a list of 8-bit bytes from these disk images, corresponding to the data that ultimately appears on the TMS5501's port 0 ("read serial data"). Evidently, just taking the middle 8 bits of these 10-bit words is not enough. How can I achieve this?

1 Answer 1


I played around a bit with it, and it seems the solution is to basically emulate the full UART behaviour, in particular in regards to detecting start bits. Instead of framing statically by 10 bits, we need to walk the bits until we find a high -> low transition (the start bit), and then we take the next 9 bits as the 8 data bits (lsb first) and a high stop bit; the only use of the latter is to check for frame errors, and also to potentially act as the high bit for the high -> low transition for the next byte.

Here's some Haskell code to illustrate the idea:

splitUART :: [Bit] -> [Word8]
splitUART = start 0
    start _ (1:bs) = start 1 bs
    start 0 (0:bs) = start 0 bs
    start 1 (0:bs) = consume bs
    start _ [] = []

    consume :: [Bit] -> [Word8]
    consume bs = case splitAt 9 bs of
        ([], []) -> []
        (fromBits -> (byte, b8), bs') -> byte : start b8 bs'

    fromBits :: [Bit] -> (Word8, Bit)
    fromBits [b0, b1, b2, b3, b4, b5, b6, b7, b8] = (bitCoerce (b7, b6, b5, b4, b3, b2, b1, b0), b8)

Applying this on track 1 of the disk image from my question, I can see directory entries like INVADEPRG and MENU BAS, convincing me that I have the decoding right.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .