#Paaliaq - 65816 computer & OS
1 messages · Page 1 of 1 (latest)
👀
i have improved memory access speed a tiny bit by reworking the state machine that handles the cpu <-> wishbone bridging
now at 125MHz i get a calculated cpu freq of 6.6MHz
Nicely done, Qookie!
i think today i will implement a wishbone master that's controlled over uart
so that i can remotely poke at memory and registers
and then also add debugging registers to the cpu interface to single step it and feed it data etc
actually, maybe i'll do it over jtag
the ecp5 fpga has a jtag peripheral with two registers available to the user exposed via vendor-specific commands
great success, i am slowly figuring out the jtag interface 
the fpga shifts out a constant 1 over jtdo, and jtdi is continuously ORed into a register wired to the led, so the second drscan turned on the on-board led
the first drscan returned fe instead of ff because afaicu the first jtdo signal is sampled at the same time as JSHIFT goes high, so i'm 1 bit late there (and the second one has the bit set because i never reset the register to 0)
being stupid hurts 
Relatable.
guess the problem
```py
m.d.comb += jtdo1.eq(shift.bit_select(0, 1))
with m.If(jce1 & jshift):
m.d.jtag_falling += shift.eq(Cat(jtdi_mux, shift.bit_select(1, 7)))
hint: related to the Cat
anyway now that i can actually send (and theoretically receive) arbitrary values over jtag lets slap some fifos on it for clock domain crossing and hook it up to the system bus
answer: Cat(a, b) is a | (b << len(a)), not b | (a << len(b)) 
lol ```
openocd: src/jtag/drivers/driver.c:166: interface_jtag_add_dr_scan: Assertion `field == out_fields + scan->num_fields' failed.
Aborted (core dumped)
okay i think i finally have something that should work
the shift register is first loaded with 0x69, then after input is shifted, 0x11 is added to it
hmm this isn't right
for some reason (probably because i'm stupid) consecutive fifo writes don't work
and only one write goes through
(0x69 is returned on reads when the fifo is empty)
i'm not sure what's wrong though, since the first fifo write works
actually maybe i'm just writing too fast hmm
all the logic is clocked by the jtag clock after all
?? the queue write ready signal goes low after writing 1 byte
oh i am once again stupid
?? how did this work
i forgot to put the fifo in the jtag domain
finally
i can now make the memory test fail remotely 

lets see, what other hardware do i want before moving onto writing actual software
i think of immediate need are a spi controller for the onboard flash, a dma controller
I think that would be really cool to see!
okay so instead of working on cool peripherals i added some registers that allow the cpu to be probed remotely
from the openocd telnet shell: ```
trace_step
trace_step
008168 008168 0f 2c 00
trace_step
trace_step
008169 008169 0d 01 00
trace_step
trace_step
00816a 00816a 0d 00 00
trace_step
trace_step
010001 010001 0e c0 00
trace_step
trace_step
00816b 00816b 0f 50 00
the values in order are vaddr, paddr, bus control lines, read data, write data
i also added registers that allow you to disconnect the cpu from the system bus and feed it read/write data directly
this is basically as close as i can get to remote debugging, since i can't just directly probe the cpu registers (but i can use the other features to execute code to make the cpu dump it's state, and return to normal operation)
now with improved output (mainly to hide irrelevant values) ```
^[[A
trace_step
00813a ?????? ---- ??
^[[A
trace_step
007ff9 007ff9 -D-W 00
^[[A
trace_step
00813a 00813a PD-R 8b
^[[A
trace_step
00813b ?????? ---- ??
^[[A
if neither vpa (signified by P) nor vda (signified by D) are high, then no bus access is happening (hence the ?)
i have to say though, tcl is one of the languages of all times
i keep thinking i should just script this in python by connecting to openocd over the socket and sending commands from python code but then i keep writing tcl anyway
Honestly, I don't think you could go wrong with Python
lol i left the memory test program running overnight with tracing enabled and it didn't even fill one bank of ram in like 7h
ig it makes sense since the tracing (disregarding the irscan and all the jtag state transitions) takes 8 clocks per sent/received byte, 6 bytes per memory read/write, and it's 10 memory accesses per step
so it's at the very least (and it's likely at least 2x wrong) 8*6*10 clocks per cpu cycle, with the jtag interface running at 1MHz (i could try clocking it faster but it's over shitty jumper wires)
so all that sums up to running the cpu at ~2kHz 
maybe you should invest in some better wires 
later on i could revisit the idea of running the debugging interface over ethernet
since the board does have a GbE phy
that would increase throughput a bit :^)
i could probably push jtag to like 3MHz
also fun fact, if you do an infinite loop in a tcl script, openocd will no longer respond to SIGINT or SIGTERM 
actually 10MHz appears to work :^)
perhaps i am underestimating shitty jumper wires
⁉️
i'm sorry i doubted you shitty jumper wires
okay the problem with 150MHz is that my jtag protocol sucks
nice, the registers to intercept the cpu's bus accesses and control them remotely also work
qookie@selenium ~/projects/paaliaq λ python scripts/test.py
read cycle 00984b vpa=True vda=False vpb=True
read cycle 00984c vpa=True vda=True vpb=True
noop cycle 00984d rwb=True
read cycle 00984d vpa=True vda=True vpb=True
noop cycle 00984e rwb=True
read cycle 00984e vpa=True vda=True vpb=True
read cycle 00984f vpa=True vda=False vpb=True
read cycle 009850 vpa=True vda=True vpb=True
noop cycle 009851 rwb=True
read cycle 009851 vpa=True vda=True vpb=True
noop cycle 009852 rwb=True
read cycle 009852 vpa=True vda=True vpb=True
read cycle 009853 vpa=True vda=False vpb=True
read cycle 009854 vpa=True vda=True vpb=True
noop cycle 009855 rwb=True
read cycle 009855 vpa=True vda=True vpb=True
noop cycle 009856 rwb=True
read cycle 009856 vpa=True vda=True vpb=True
read cycle 009857 vpa=True vda=False vpb=True
read cycle 009858 vpa=True vda=True vpb=True
...
this actually gets me pretty close to doing one of the things i wanted to do once i get a physical 65c816 hooked up
and that is checking https://github.com/SingleStepTests/65816 against it
it also means i can remotely query the register state, by feeding it instructions that store the register values somewhere and intercept that
and then at the end just resuming normal execution by executing a jump instruction to the original instruction pointer and disabling the debugging functionality
i was wondering why a simple debug program that just executes JMP 0x8000 and then resumes didn't work, and the problem is that i didn't reset the data bank, so it tried accessing 0x01802b instead of 0x00802b, which is unmapped
so it was just getting stuck and holding up the bus
Idk about 65816 but testing bus activity per cycle sounds a little odd no? Imo a good verification interface + bunch of illegal states in the design is super important for verification
? the 65xx processors can do one memory access per clock cycle, and the 65816 added output pins that say whether the cpu is actually doing a bus access that cycle
so instead of performing extra reads or whatever which have side effects (which is why you usually avoid instructions like INC on mmio), you just do nothing for that cycle
Oh I thought you're implementing an implementation for the ISA and not making an equivalent for the implementation, yeah that makes sense then
i'm designing all the stuff around the cpu, and using an existing implementation (and later on plan on using a physical w65c816 hooked up to the fpga)
Ahh fair enough
hmm i was kinda thinking about the mmu, and i think i'm gonna redesign it to be hw refilled
instead of doing that i have done some work on the toolchain
the linker more specifically
now it can accept multiple input object files, and can handle collecting multiple sections from the input files 
it's currently still limited to only one output section though, and i don't emit any phdrs, but it's still an improvement
i have been thinking about implementing a small cache in front of the sdram
probably nothing crazy, just a few lines of either direct mapped or 2 way cache
likely with 32 bytes per line, dictated by the sdram being 32 bits per word and supporting burst reads/writes up to 8 words (with 1 word per clock after CAS delay or w/e)
or well, i could also do full page bursts and issue a burst stop after however many words
but i feel like that'd make the logic too complicated
dead project
i have started doing some work again yesterday
i ordered a yet another fpga board, the icestick-pro (which despite the name does not use an ice40 fpga but the same ecp5 one as before) and i've ported the rtl to work on it
which required updating the pinout, reworking the hdmi code to not use differential pair outputs because the dev carrier board wires up hdmi to random pins and not diff pairs, and reworking the memory controller to support 16-bit sdram chips with dqm wired up
i went with a new board mainly since it has a built-in programmer so i don't need to include one on the pcb i'll be designing
and it also has larger sdram and spi flash (32MB each vs 8MB and 2MB respectively on the previous board) and is in general wired better (sdram has dqm actually wired up to the fpga, spi flash has all the pins connected so i can use qspi)
also has a sd card slot and a rgb led :^) although it is missing the two GbE phys so win some lose some
i also quickly wired up the cursor position to CSRs so it can be moved by software
few updates since last time
i introduced a nasty cdc bug in this and i'm surprised it worked fine
and i also finally got around to doing this
i got the hw side done, and i'm now writing tests to see whether the hw refill works
i also spent some time earlier on fixing some timing violations so now the timing passes at 100MHz instead of 75MHz, which means i can actually run it at 150MHz
(although that ofc will vary with PVT)
although otoh the register writes can't happen fast enough for it to be noticeable so at worst it'd be broken for a few pixels
since the cdc was an unsynchronized crossing from the soc domain (at 100MHz) to the pixel domain (at 65MHz)
hmm the new mmu seems to work fine
although the page fault test is odd because i'm reading from the fault reason register and then printing the value, but i'm instead seeing the address of the register ???
oh i was writing into the fault register on the hw side even if no fault happened
the mmu test:
- loads a identity mapping new page table and flushes the whole tlb
- maps VA 0x200000 to PA 0x800000 as RW, flushes the tlb entry for that VA
- writes something to 0x200000
- maps VA 0x200000 to PA 0x801000 as RW, flushes the tlb entry for that VA
- writes something to 0x200002
- prints the contents of 0x800000 and 0x801000
- unmaps VA 0x200000, flushes the tlb entry for that VA
- tries to do a read and a write from 0x200ABC
the fault reason register contains the whole 24 bit address and flags whether it's due to a non-present page, a write, an instruction fetch, an user access
addresses [0x800000, 0xffffff] are identity mapped in hardware so that there's an implicit direct mapping for all of sdram
i wanted to see if the bit fade test in the memory test actually catches anything, so i disabled refresh in the sdram controller, but after doing that it gets stuck reporting failure in one of the tests
it prints the same line (which is not the one it should be printing either) in a loop
what's strange is that i'm not storing any state in sdram
so i don't quite get why this happens
i've started working on an spi controller yesterday and finally got something working
0x9f is the read jedec id command for the spi flash chip on-board
the current interface is really simple:
set bit 0 of 0x010600 to start a transaction, for each byte in the tx fifo (push via csr at 0x010601) shift it out and shift in a byte into the rx fifo (pop via csr at 0x010602)
although i have plans for a more complex interface that allows for dspi/qspi support and plays better with dma (and allows skipping reading/sending dummy bytes, like the first 0xff that's received, or dummy bytes between the address and data in the fast read command)
and also i'll need to implement a frequency divisor in the spi controller, since i plan on using it for the sd card as well, and those need 400kHz during init, and 20MHz during normal operation (vs the 75MHz i'm driving the spi flash at rn)
after the spi controller i think next thing i'll implement is the dma controller, then i was thinking a simple usb ls/fs controller (primarily for keyboards so you can have a full local console together with the video output i already have)
then ig finalizing the top-level soc design (instantiating more uarts etc) and that should be all the hardware that needs implementing for now
another thing i was considering but i'm not certain i'll implement is some sort of audio output
either just a dac fed by dma or some sort of fm synth or psg
i was also considering trying to somehow get a pixel framebuffer going (vs the current text mode one), but for that i'd likely also need to use dma since there is not enough block ram to fit it in
but then the problem is that it'd need special arbitration (to give more priority to the video dma vs just round robin) and ideally also burst reads since a single read would take too long
hmmmm
where is the bit going
it's gone in all bytes sent from host to the soc over jtag
(incl the bytes making up the address)
i'm confused
how did lattice manage to make such a confusing hw block
i might just implement a whole jtag interface since the adapter on the fpga board i'm using has a second jtag connected to some generic ios on the fpga
instead of trying to use the fucking JTAGG which has stuff registered on the clock negedge
stupid hack for now, make DR 9 bits and ignore the top bit :^)
i'll think about it later
what fpga is this running on? or is it the actual chip?
lattice ecp5
LFE5U-25F-6BG256C more specifically
although i do have plans to hook up an actual w65c816 chip to the fpga in place of the soft core i'm using right now
current usage stats (skipping stuff that's completely unused) are ```
Info: Device utilisation:
Info: TRELLIS_IO: 57/ 197 28%
Info: DCCA: 5/ 56 8%
Info: DP16KD: 48/ 56 85%
Info: EHXPLLL: 2/ 2 100%
Info: IOLOGIC: 9/ 128 7%
Info: SIOLOGIC: 3/ 69 4%
Info: JTAGG: 1/ 1 100%
Info: USRMCLK: 1/ 1 100%
Info: TRELLIS_FF: 1913/ 24288 7%
Info: TRELLIS_COMB: 4226/ 24288 17%
Info: TRELLIS_RAMW: 20/ 3036 0%
also i had a think about it and i realized that perhaps there is an extra clock cycle between shift-dr and update-dr where neither signal is active
which would explain why it might be broken on this board but not before (since before i was using a different jtag adapter)
re. why it matters, due to the fact TDI is registered, the fpga fabric sees the value as it was one TCK edge ago, so we need to shift one more bit into the register the cycle Shift-DR ends
and previously i had the condition shift | update since Update-DR followed Shift-DR immediately
but now i've implemented shift | shift_negedge and that seems to work without the 9 bit hack, so i guess this jtag adapter just does nothing for one TCK edge between shift and update?
(i don't know the details of jtag since most of it is handled by the hw block, so perhaps this explanation is nonsensical)
right, back to spi for a little bit before i need to pack
there is still one issue i need to figure out, namely that the final byte is not written into the rx fifo (or maybe it's not being transmitted correctly from the tx fifo?)
oh i see
yep, the logic for terminating the transfer was skipping the final tx fifo byte
nice, reading from the flash also works
so i've started work on this, and it seems i have an off by one error in reception :^)
i get all the bits as expected, but shifted by one (and the rx fifo should only have 3 bytes instead of 4)
ah i see why
i just need to insert a one spi clock cycle delay for the transmit -> receive segment turnaround
i got the segment stuff working well enough to be able to do what i originally wanted to do
first i put in the command bytes into the tx fifo (0x0c - fast read with 32-bit address, 0x00 0x00 0x00 0x00 - the 32-bit address to read from), then define 3 transaction segments: 5 byte tx segment, 1 byte no-op segment (no activity on data lines, but the clock is still running), and a 64 byte rx segment
and then finally pull bytes out of the rx fifo, this time without the junk 0xff bytes before the actual contents of the flash
i still want to implement 2x and 4x transfer bit widths but that should be relatively simple in comparison
and now with support for 2x and 4x transfer modes
here i'm using the command that in addition to sending the data at 4x rate also expects the address to be given at a 4x rate
first i'm pushing the command, 4 address bytes, and the dummy M7-0 byte into the tx fifo, then writing the segment descriptors: 1x rate transmit of 1 byte, 4x rate transmit of 5 bytes, 4x rate no-op for 2 bytes, 4x rate receive for 64 bytes
then pulling the received bytes from the rx fifo
only thing left is the clock divisor to support slower devices
for the clock divisor logic one thing i wonder about is what time to sample/switch the lines
at full throttle there isn't much choice but for example with a /10 divisor there are a bunch of cycles to choose from
i'm leaning towards doing it in the middle of the cycle
since that'd be well ahead of the incoming setup time and well past the previous hold time
oh i just thought of another bug
in a transaction ending on a tx segment, we're missing the final spi clock pulse
since we determine that the segment is done in the clk->0 state, after having transmitted 8 bits total, but for tx segments this is before the corresponding clock edge for the bit
Do you have a verification framework? It'd be difficult to catch all of the bugs via waveforms
not really, i've been meaning to get into verifying the designs i've written but i haven't gotten around to it
the current verification strategy is: it works => good enough
I mean its a good idea before the design is a little too complex to quickly find the errors, as far as I know it always saves time in the end
yeah thats true
so far ive been getting away with not doing proper verification so i guess thats why i havent gotten around to it :^)
Fair enough
fixed that while doing the preparation for the clock divisor support
now all that's left is just adding the counter registers
another thing i was thinking about is configurable cs hold time
since one thing i want to hook up over spi is the enc28j60 ethernet mac+phy
and it has both a max clock speed of 20MHz, and for phy register accesses it has a cs hold time of 210ns after the last clock pulse
so i was thinking of either having cs be controllable by the user, or adding support for holding cs before and after the transaction for a certain amount of time
and i'm leaning towards the latter, by putting some fields in the config register
probably in units of (maybe half) clock cycles (although without the clock being active)
hmm for tx this is fine, but for rx this is way too late
the data is only really held until sck negedge and is invalid in the middle of the time between cycles
hmm
hm, i could redesign the controller to be slightly slower but make the logic significantly simpler
by requiring that it's at minimum /4 of the soc clock (vs /2), and then splitting the tx and rx logic across the different clock edges
so tx would remain where it is now, but rx would happen somewhere during the sck being high instead
i don't think 75MHz vs 37.5MHz for accessing flash matters much on a system where the cpu runs at 6MHz :^)
fascinating, sigrok does not have dspi/qspi support
nor does it's spi flash decoder understand fast read with 32-bit address
anyway, the refactor is ongoing and i have tx working correctly
but rx isn't
... rx isn't working because i'm sampling mosi and not miso
nice, it now all works
and it's also a net reduction in code size
the diff is +90-150
i also decided to stop guessing and have a proper look at the datasheets
and the data out from the device is ready at sck posedge so i was overcomplicating it needlessly earlier

first 8 bits is the command (0xEC), then next 32 bits (sent at 4x bitrate using 4 lines) is the address (0x00000000), then next 8 bits (at 1x bitrate) are just the dummy clocks needed by the flash, then next 16 bytes (sent at 4x bitrate) are the data from the flash
sck is running at 292kHz, and the logic analyzer at 24MHz
24MHz is not enough to properly see setup/hold times for data coming from the flash, but it is enough to see that it's already ready the instant SCK goes high (and the datasheet corroborates this)
anyway, i am satisfied with this
i've been thinking of starting work on the kernel given that i have most of what i want the hw to do implemented
although before that i've been thinking of making some simple language that's a bit higher level than assembly to simplify the process of writing the kernel
probably again as a set of scheme macros and procedures
another thing ive been thinking about is zmodem support in the firmware to upload the kernel image
instead of the dumb serial upload i have now (mainly because it doesnt play nicely with picocom)
i finally got some time and motivation and i've written the world's worst zmodem receiver
doesn't do any crc checks, assumes only one file is sent, excepts the full file data to be sent in one ZDATA block
but now i can send a binary to execute by just doing C-a C-s <filename> in picocom
all that in a hair under 400 lines of assembly :^)
test is a small binary that just writes A to the uart tx fifo 4 times and halts
nice, seems that it also works correctly at 115200 baud even for extended transfers
i sent a 1MB file and there was no uart rx fifo overflow
pushed the code for it https://github.com/qookei/paaliaq/blob/main/fw/zmodem.S.scm
nice!
now add a crc, revise the protocol slightly, and call it xmodem 😉
given the fact it even seems to work fine even without an rx fifo i'd say there probably is enough time to update the crc between bytes
even if i don't really care about validating the headers since i don't look at anything but the type byte to make sure we're in the right path along the state transition diagram, but it'd be nice for file data
cool!
i was just joking though, because zmodem with only 1 file, and i assume discarding the filename, is the about the same feature set as xmodem
yeah i mainly picked zmodem since picocom defaults to sz, and the zmodem transmitter is specified to send a sequence to trigger the receiver (which is not something xmodem does? i didn't see any mention anyway)
which is nice for this use case (uploading a kernel binary to memory)
as for the file name, i was considering parsing it to get the load address instead of hardcoding 0x800000 :^)
but otoh that's good enough for me so w/e
oh i see. i have heard that xmodem was very clunky
parsing for the load address? could be fun, but you could equally have subroutine libraries relocate themselves when loaded
so i started working on the initial sketches of the kernel
so far i wrote a simple free list based pmm, with some consideration given to the fact i'll likely want a struct page (currently reserving 16 bytes per page, and 2 of them for the free list link)
yesterday i ran into a funny bug in my zmodem impl
i accidentally mistyped xoff as dc4 instead of dc3, and the binary i was receiving had a crc16 ending in 0x14 (dc4) which caused my code to skip a byte and then abort the transfer because the bytes afterwards were wrong
oh no lol!
i couldn't help myself and bought another fpga board from aliexpress
this time featuring an artix-7 100t fpga
so far i've done some basic porting work and have the core soc, hdmi, sdram (at <133MHz) working, although i had to comment out the jtag debug interface and spi controller because they used primitives specific to ecp5
and the sdram is technically rated for up to 166MHz at CL3 but i just get nothing out of it at that speed
140MHz with CL3 also works, hmm
okay plans for some upcoming work:
- rewrite the memory controller to decouple it from the soc clock domain and introduce a cache (and also clean up the code since i dont like it)
- port the jtag debug bridge and spi controller to series 7 (latter just needs changes for accessing the on-board spi flash clock)
- actually decide on the peripherals i want to include in the soc because it keeps being in flux which results in the rtl being constantly somewhat done but not quite
- figure out stuff like how many uarts i want etc
- decide what new stuff to add (candidates are a gmii mac, since the new board i have has a gmii phy, i2c controller, usb host controller - at least good enough to handle an usb keyboard to make having a local console work sensibly)
some good to haves:
- full system simulation (i have some ideas on how to architect the code for this, although some stuff like no sdram model etc are a bit annoying)
- dv (both unit and integration)
- docs :^)
bit of an update:
a bit ago i've ordered some w65c816s chips from aliexpress (since it was quite a bit cheaper than getting them from mouser)
so far i've tested one on a breadboard using a nop generator and it appears to function as a 65c816 (as opposed to a 65c02 or not at all)
here's a photo of the testing setup: the rpi pico is acting as a clock generator, and the logic analyzer is probing some bus control lines (some of which are specific to the 816)
and here's the logic analyzer capture showing a reset sequence and the nops being executed
the trace confirms that it's an 816 and not a 02 since VDA is behaving as expected, while on the 02 it would've been a clock output instead
given this, i've also very quickly threw together a pcb that has a pin header for my fpga board and a socket for the cpu to hook it up
and i had it manufactured with jlcpcb ($10 all in including shipping) and am waiting for it to arrive now
the nop generator is just some pull up and pull down resistors on the data lines to force a value of 0xEA, which is the nop opcode
wired through pull ups/downs to not have a short when the cpu drives the data bus to present bits 16-23 of the address
one mistake i realized like yesterday though is that due to the large default keep outs on copper fills around PTHs, i don't have ground under some of the traces, which is going to negatively affect signal integrity
but i'm hoping that it's gonna be good enough, given the pcb is quite literally just a pin socket, a dip40 socket, 1 decoupling cap and 1 pull up resistor
and it's only gonna run at 20MHz at most (fingers crossed), and i've done higher speeds over jumper wires for stuff like spi or jtag so i'll take my chances 
and all the traces are on the top layer so the bottom layer is just a full ground plane
okay, so doing some more testing on the 65c816 chips i have it becomes even more obvious that they are not w65c816s8p-14 parts (although the package markings already make it pretty clear given the chips have been clearly rebadged)
using a faster logic analyzer (slogic16u3) i was able to measure some timings, and i tested them with faster clocks
and what i found out is that the address setup time is about 50ns, which is well above what wdc reports as the maximum for this chip (max is 30/40ns)
and that the chips don't run reliably above 3.75MHz
which would be consistent with them being rebadged chips from the 90s
i guess if someone bought them to put in their apple 2gs they wouldn't notice unless they were putting them in an accelerator card 
interestingly the two chips don't appear to even be the same (which ig shouldn't be surprising)
the other chip appears to run fine at 6MHz, and the bank address setup time is 40ns instead
i guess eventually i'll still have to go order some w65c816s off of mouser but for now this is good enough
if i had to guess, i'd guess that these two chips might be rebadged gte g65sc816 chips (4MHz and 6MHz ones respectively)
got a refund for the two chips since they're not what was advertised, for the whopping 25pln i paid for the two of them, and i get to keep them 
and in other news, the sipeed slogic16u3 is a very nice logic analyzer
some more progress on the rtl work
im continuing some refactoring and i have reenabled the spi controller for the on-board spi flash on the artix 7 board
and it works, or at least i can read from it (with divisor>0 => speeds <=50MHz)
i was working on making the text video output 256 color and it appears i have a bug in my hdmi impl
the colors correspond to the 256 colors accessible via ansi escape codes
if i display any more colors in the square (currently it stops before 248) the monitor loses sync
the broken pixels continue until the end of the line, since during {v,h}blank the encoder is bypassed and it's outputting fixed symbols
hm one potential issue identified
nice how did I not know this was a thing
hm it has both helped and made things worse
diff --git a/rtl/paaliaq/hdmi.py b/rtl/paaliaq/hdmi.py
index 4a95861..e668572 100644
--- a/rtl/paaliaq/hdmi.py
+++ b/rtl/paaliaq/hdmi.py
@@ -46,10 +46,10 @@ class TMDSEncoder(wiring.Component):
with m.Else():
with m.If(in_balance[3] == total_balance[3]):
m.d.comb += out.eq(Cat(~data_min[0:8], data_min[8], 1))
- m.d.sync += total_balance.eq(total_balance + data_min[8] - in_balance)
+ m.d.sync += total_balance.eq(total_balance + 2 * data_min[8] - in_balance)
with m.Else():
m.d.comb += out.eq(Cat(data_min, 0))
- m.d.sync += total_balance.eq(total_balance - (~data_min[8]) + in_balance)
+ m.d.sync += total_balance.eq(total_balance - 2 * (~data_min[8]) + in_balance)
with m.If(self.active):
m.d.sync += self.data_out.eq(out)
hello
case in point
color 243 is now fine, but 4 and 255 aren't :^)
another potential issue found
wait how did this work in the first place lol
there we go
the failure mode here was very interesting too
my guess is that due to loss of dc balance the receiver couldn't lock on properly temporarily and a bit slipped until it resynchronized at hblank
and since the colors that failed were gray (apart from the blue, hmm), all channels had the same value, so all 3 encoded streams had the same fault
full fix: ```diff
diff --git a/rtl/paaliaq/hdmi.py b/rtl/paaliaq/hdmi.py
index 4a95861..de8bc82 100644
--- a/rtl/paaliaq/hdmi.py
+++ b/rtl/paaliaq/hdmi.py
@@ -31,25 +31,27 @@ class TMDSEncoder(wiring.Component):
for i in range(1, 8):
m.d.comb += data_min[i].eq(self.data_in[i] ^ data_min[i - 1])
-
in_balance = in_1_cnt - 4
-
min_1_cnt = sum(data_min[0:8]) -
min_0_cnt = 8 - min_1_cnt -
min_balance = min_1_cnt - min_0_cnt total_balance = Signal(signed(4)) out = Signal(10)
-
with m.If((in_balance == 0) | (total_balance == 0)):
-
with m.If((min_balance == 0) | (total_balance == 0)): with m.If(data_min[8]): m.d.comb += out.eq(Cat(data_min, 0))
-
m.d.sync += total_balance.eq(total_balance + in_balance)
-
m.d.sync += total_balance.eq(total_balance + min_balance) with m.Else(): m.d.comb += out.eq(Cat(~data_min[0:8], 0, 1))
-
m.d.sync += total_balance.eq(total_balance - in_balance)
-
m.d.sync += total_balance.eq(total_balance - min_balance) with m.Else():
-
with m.If(in_balance[3] == total_balance[3]):
-
with m.If((min_balance > 0) == (total_balance > 0)): m.d.comb += out.eq(Cat(~data_min[0:8], data_min[8], 1))
-
m.d.sync += total_balance.eq(total_balance + data_min[8] - in_balance)
-
m.d.sync += total_balance.eq(total_balance + 2 * data_min[8] - min_balance) with m.Else(): m.d.comb += out.eq(Cat(data_min, 0))
-
m.d.sync += total_balance.eq(total_balance - (~data_min[8]) + in_balance)
-
m.d.sync += total_balance.eq(total_balance - 2 * (~data_min[8]) + min_balance) with m.If(self.active): m.d.sync += self.data_out.eq(out)
final result, 256 colors :^)
now just need to implement ansi escape sequence parsing :^)
lets see, since i'm on a roll tonight, maybe i can figure out another bug
for some reason, my dump-256-bytes routine gets stuck printing the same 16 bytes if you call it multiple times :^)
hm, doesn't happen if i disable writing to the text framebuffer
okay i think i see why
i'm not preserving the y register correctly across print calls
and it ends up getting corrupted by video-scroll
i ended up fixing it btw
what are you running this os on, hardware platform wise? i only know of one system with a 65816 that is common and thats a SNES?
custom platform, the project involves both designing the hardware and writing software for it
with more focus on the former at the moment since im porting it to a new fpga board i got
nice, lots of breadboard?
nah a lot more boring, just an fpga, and soon enough a daughterboard hosting a physical 65c816 cpu
the pcbs for it arrived and ill solder then together this weekend
ah i didn't realize you want to use a separate cpu
what will you do with the fpga once you have the cpu?
well the fpga is still the rest of the owl (all the peripherals, memory controller, etc)
the owl? 
ah i see
it is perhaps a bit weird design wise but eh, i think its cool
do you use the discrete cpu to free up fpga space or is it just faster?
neither really, the only thing it saves me is the fact i'd need to implement abort support in the soft core im using right now
if i was from the ground up planning for this to not use an external cpu i probably would end up writing my own soft core that uses the 65816 instruction set but not the same bus interface or timings
for larp purposes think of this as a unix-ish workstation, with the fpga being a custom asic 
the pcbs have arrived
or well, they arrived on monday but i had them delivered to my parents' place since i wasn't home during delivery hours within the week, and i have my soldering iron etc here anyway
2 dollars to have 5 pcbs manufactured, and 8 dollars in import and shipping 
i'll put one or two together tomorrow, then i can get to testing sunday evening (since i don't have the fpga board here)
i am pondering reworking the text video output such that instead of just getting a 24K framebuffer, to instead a command-driven interface that maps relatively closely onto ansi escape sequences
such that the cpu only needs to do ansi escape sequence parsing and pushing commands into the command fifo
main motivation is that scrolling is really slow, since it has to shovel 24K of data, and the fastest i can do that is 7 cpu cycles per byte copied (with the MVN instruction), which ends up taking a while
okay well 2 days later i put one together, and even bothered to wash it in ipa after soldering :^)
i am not putting another one though, i need a better soldering setup
and i don't feel like soldering another 84 joints with the shitty soldering iron i have
i'm trying to get the cpu working with the fpga board, and it's doing something
something is defo wrong because it never writes to the uart, and if i configure PULLTYPE="PULLUP" for the ios it hangs on the first actual read
some more progress
now just to figure out why it's aborting
i think i need to add in the debug bridge to the design so i can step through individual cycles and look at the bus state without needing a logic analyzer
also a pic of the setup :^)
hmm probing around the irq, nmi and abort lines doesn't reveal anything
i also tried adding nops around the insns and they do execute and push back the point it fails at back
hmm
hmmmm
i have a hypothesis
one of the data bus bits is misbehaving
and instead of 0x8F it ends up seeing some opcode that has no operand bytes
then it ends up treating the following 0x02 as an opcode, and 0x02 is COP (software-generated interrupt)
okay after more probing and some thinking, data bus bit 2 is never driven high by the fpga
all the instructions that execute prior (0xEA, 0x18, 0xFB, 0xA9) do not have bit 2 set, and the first instruction that does is the store (0x8F)
if i clear bit 2 of 0x8F i get 0x8B, which is PHB (push data bank register)
which takes no operands, and writes one byte to memory (which can be seen on the logic analyzer trace, there is a cycle with VPA=0, VDA=1 after the cycle with VPA=VDA=0)
... i misread the schematic and had the wrong pin assigned
- data="U20 Y21 Y22 W23 AC24 AB25 W24 AC26",
+ data="U20 Y21 V22 W23 AC24 AB25 W24 AC26",
it works \o/
peam
hmm one thing i need to verify is how exactly aborts behave
it's underdocumented in the actual datasheet and i didn't see many references online
one thing i can see for example that differs from what i expected for example the fact that after aborting an instruction in the middle, the following cycles of the insn still have valid VDA and VPA
one question is: if a 16 bit value load straddles a page and only one cycle is aborted, does the unfaulted byte have any effect on registers
like, in this example (assuming 0x200000 is mapped and 0x201000 isn't) lda (imm #xAABB) lda (far-abs #x200FFF) will A equal 0xAABB, or 0xAACC (assuming byte at 0x200FFF is 0xCC)
as far as i can tell, the answer should be no, but it's an open question
likewise if the high byte of the 16-bit value is unfaulted, and the low byte is faulted
likewise with operands of an instruction
for a load or a store it doesn't really matter what happens since the whole insn is retried so it'll just load again, but it does matter if an add/subtract/etc faults
given that the alu is supposedly fully 16 bit, as is the internal data bus connecting all the registers and alu, i am inclined to speculate that the loaded value first fully ends up in a temporary register, and then is transferred at once after the second load completes
i have done some tests (and i have improved the abort handling in the rtl) and it does appear to work like this
the only thing to be wary of is that for stores that cross a page boundary, and the low byte is in a valid page, that write will still go through
but it doesn't quite matter unless the write has side effects, but at least for mmio i've planned it out so that mmio fits within a single page
65816 compooter 👀
okay i think i ironed out all the kinks in the abort handling
the faults in order are:
trying to write to within an unmapped page
trying to write across a page boundary, where the low byte lands in an unmapped page
trying to read from within an unmapped page
trying to read across a page boundary, where the low byte lands in an unmapped page
trying to read across a page boundary, where the high byte lands in an unmapped page
trying to perform an indirect read where the pointer resides in an unmapped page
trying to execute code from an unmapped page
the fact that the A register remains unchanged across the load faults means it behaves as expected
the logic to handle this is kinda a mess and i think i'll eventually revisit it (and rewrite the entire cpu interface logic) but for now i'll leave it be
i've been kinda dragging my feet on this but i've finally written an uart-based debug bridge
as a replacement for the jtag one i had before because i'm too lazy to port the jtag primitive gunk to series 7
i think this concludes the porting branch (well, i didn't port over the temp sensor but w/e, i'll get to it later)
the branch got out of hand and it's time to merge it into main
also good to see that the debug logic in the cpu interface hasn't rotted away
i think i'm gonna take a little detour and run the 65816 emulator test suite on a real cpu using this debug interface
it something ive been meaning to do once i get a real cpu instead of the softcore
okay after a few hours of work (and sleep inbetween
) i have a test runner working using the debug interface
including the code to load the initial cpu state and to dump the final cpu state
it's pretty slow but it does work from what i've thrown at it so far
hmm running a test for a single instruction (with the cpu state setup and retrieval) takes like 9s 
hmm i am trying some tests right now and there is a very strange issues
tests for LDA/LDY with immediate that have A or X/Y (respectively) set to 8 bits wide behave weird
the cpu fetches the opcode and first operand byte as expected, the proceeds to fetch the next operand byte (which it shouldn't do), then has 3 internal operation cycles (which no instruction does), then loads a value (not an operand) from pbr:FFFF
hmm the load immediate instructions also behave weird in emulation mode
this is very weird
hmm
i am still messing with this and the issue doesn't occur if i run the code from actual ram
oh and i think i see why
in an edge case where a read takes a very long time to complete (like it does with the debug bridge enabled), setup time of the data bus will be violated
because the logic is ```
while read is in progress:
if ctr < read_time_ctr:
ctr += 1
elif read complete:
start next clock cycle
i am very surprised that so many instructions executed without issue and it's only the load immediate instructions that fail, and only when the destination register is in 8-bit mode
yep now the test passes
very interesting mode of failure
hm this might explain why it also didn't work at >4MHz? will need to investigate ig
hmm
now i have a new issue
the test suite includes 20k tests for each opcode, 10k in emulation mode, 10k in native mode
with the simplest tests taking ~9s each, running the whole suite would take nearly 2 years
the biggest bottleneck is the debug interface link, which i can at most crank up to 12MBaud (although that is probably pushing it, since the usb link is also 12Mbit/s, and it has additional overhead)
i guess i need to write a gigabit ethernet mac and implement a network-based interface 
or i guess i can just take a random sample of tests from the suite :^)
okay well now we wait ig
actually i think i'm already bottlenecked by usb fs itself, raising the baudrate from 2MBaud to 4MBaud didn't improve speed at all
after some more fixes for unexpected bugs (and forgetting to skip the halt opcodes from the test
) i even found some actual issues in the test suite
e.g. ```
CPU bus activity differs from expected activity:
expected | actual
1. a39cbe 7c dp-r | a39cbe 7c dp-r
2. a39cbf 94 -p-r | a39cbf 94 -p-r
3. a39cc0 44 -p-r | a39cc0 44 -p-r
4. a39cc0 ?? ---r | a39cc0 ?? ---r
5. a3d66a 76 d--r | a3d66a 76 -p-r <-- different
6. a3d66b 7c d--r | a3d66b 7c -p-r <-- different
Test "7c n 3787" FAIL, in 9.66 seconds
the datasheet says the last two cycles should have VDA=0, VPA=1 (like in the actual column)
i also noticed one more issue with the test runner itself (or rather the logic in the fpga itself), namely it doesn't collect the cpu write data if not (vda or vpa), while the test suite wants to compare the data, which has caused some failures
fixed this (just one more condition bro)
after 13 hours i have results:
All done!
Summary: 4671 successes out of 5040 tests (92.68%).
probably never since they didn't manufacture them and i'm using an actual cpu connected to an fpga :^)
see here
Ahhhh
Fair lol
Ooooo based
So does the FPGA handle like Audio and graphics stuffs?
Also, why use a real CPU if you have the FPGA?
yeah the fpga implements a page table based mmu, a memory controller, all the peripherals, etc
as for why, there isn't much reason other than i wanted to do it :^)
ig it also saves me from having to fix/make a 65816 softcore, since the existing ones don't support the abort input
and the mmu uses that to signal page faults such that the faulting instruction can be retried
the fact i'm using a real cpu might also raise questions as to why i'm running cpu tests, and the answer is that i wanted to validate the test suite against real hw since it was developed using an emulator (and is already known to have other issues)
ig now to go through the 369 failures and see what's up
recently i did a bit of shopping on aliexpress, and i bought an usb hdmi capture device
and it turned out to be really strict about the signal it's getting being proper hdmi and not a broken mess 
like, it needed proper sync signals, and proper control symbols 
jokes aside, bugs in my impl were found and i am surprised my monitor synced to this in the first place
but now i don't need to switch monitor inputs anymore
(don't mind the wonky scaling, i just need to switch guvcview for something else where i can set the window resolution easily)
another thing i got is a gigabit usb nic so i can experiment with ethernet
huge
by connecting the usb nic to your host? or how does it help?
yeah the main idea is not having to connect it directly to my router
both so i don't have to run a cable and so that it's on its own network (that i can just nat or bridge over to the internet if i want to)
hmmm
i am wondering what i want to do now
thinking of either trying to get gmii working or working on the video subsystem
video
on the video side i wanted to do two things
first, instead of exposing the raw text framebuffer i wanted to instead provide a command interface that handles all the terminaly bits
one thing i'm pondering here is whether i want to go all out and implement ansi escape sequence parsing in hw or a simpler command interface :^)
and the second thing is that i keep thinking about using the sun gallant font, which would require rewriting the logic since it's a different size (and not even powers of two)
with that i was also wondering if i should maybe raise the resolution to fit more text, currently it's 128x48 characters at 1024x768, but with the gallant font it'd only be 85x34 at that res
hmmm at 1080p it'd be 160x49 characters 
i guess i can always do it in two steps, first implement the custom command if, then later implement a parser module and wire it up
main reason why i want to do this is to offload the cpu, primarily with scrolling the screen
fascinating
i love Q@RAM
scrolling appears to work great too

wtf is going on tho
for reference the same thing written to uart on the left
oh i didn't put the framebuffer read and write ports in the pixel clock domain
we do a little unsafe clock domain crossing
i'm surprised i got the scroll logic right on the first try
at least for the 1 line down case
okay i got a basic ansi escape sequence parser and it works for the most part
todo: scroll down is broken (new lines aren't empty)
is the parser now in hw or sw?
hw
