I haven't had a lot of time recently, but I wanted to add a video output circuit as I was tired of the slow speed of the serial communication. I could increase the baud rate but it still wouldn't get me to where I want it to be, it ends up being too much of a burden on the system and all the user processes end up spending all their time waiting for the serial line.
I won't go into full detail about the video circuit here, I'll post that separately when I get time - but briefly, I wanted something capable of decent text output, without requiring a lot of video RAM, so went for a VGA-style text display, with 80x30 character cells, each 8x16 pixels. To keep it simple at first I also made it black-and-white only, with no character attributes. As I've said before, all my past video circuits were synchronised with the CPU clock - or rather, the CPU clock was generated from the video circuit. It's a pattern I copied from the BBC Micro. It works well but removes a lot of flexibility on the CPU side of things, and I've been meaning to do it differently "next time" - so here next time is. This runs the video circuit at 640x480 VGA frequency, and the main computer is still running at 4MHz. The video circuit captures all writes to paged memory, and especially writes to pages $F0-$F7, and buffers these (1-deep) and writes them into its own private video RAM at the next opportunity. The computer's main RAM is also accepting these writes, and the CPU can also read the data back from there like any other RAM.
Here's a photo of the circuit and test output:
Here the kernel is mapping one of its logical pages to physical page $F0, which the video circuit decodes as video memory, and writing data there as part of its initialisation sequence. I used the chequerboard pattern to check the screen bounds were good and make sure the monitor's calibration worked well, and overlaid a ruler so I could check the overall width was correct.
Code: Select all
videotest:
.(
; Map video memory at LP1
lda #$f0 : sta PT_LP1W : sta PT_LP1R
stz zp_ptr
ldx #>LP1 : stx zp_ptr+1
ldx #30 ; count 30 rows
loop2:
ldy #0
loop:
; convert column number to hex
tya : and #15
cmp #10 : bmi skipletter
adc #6
skipletter:
adc #48
cpx #16 : beq is16 ; if this is row 16, display the hex digit
lda #$b1 ; otherwise display the chequerboard pattern
is16:
sta (zp_ptr),y
sta (zp_ptr),y ; extra write to work around hardware bug
iny : cpy #80 : bne loop ; 80 columns
clc : lda zp_ptr : adc #$80 : sta zp_ptr ; advance to next row (stride = $80)
lda zp_ptr+1 : adc #0 : sta zp_ptr+1
dex : bne loop2 ; stop after 30 rows
rts
.)
I will probably add a syscall to allow any user process to also map this page, and they can just fight over it - all I want is to use it for more real-time output from the processes, so I can design them to each draw only in one part of the screen. In future I could use more RAM pages to provide multiple framebuffers, with some form of switching capability like virtual consoles in Linux, but the point here for test purposes was to see them all running together so this is what I'll do first.
There is a bug at the moment which leads to some write operations not being performed, or perhaps not being performed correctly. I'm not sure what's causing it, it is hard to observe happening, and I tried various mitigations at various points in the write pipeline without improving matters. Making the code just execute every write operation twice in a row did seem to work around the problem at least - I will have to diagnose it in more depth another time.
I am also pretty sure my shift register is broken - it behaves very strangely, in ways that I have also found workarounds for but which shouldn't be necessary. I mean to also try swapping it for another one but it is trapped under wires at the moment!