#Paaliaq - 65816 computer & OS

1 messages · Page 1 of 1 (latest)

tired yacht
livid briar
#

👀

tired yacht
#

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

livid briar
#

Nicely done, Qookie!

tired yacht
#

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

tired yacht
#

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

tired yacht
#

great success, i am slowly figuring out the jtag interface meme

#

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)

tired yacht
#

being stupid hurts meme

livid briar
tired yacht
#

guess the problem meme ```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

tired yacht
tired yacht
#

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

tired yacht
#

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

tired yacht
#

i can now make the memory test fail remotely meme

livid briar
tired yacht
#

lets see, what other hardware do i want before moving onto writing actual software

tired yacht
#

i think of immediate need are a spi controller for the onboard flash, a dma controller

livid briar
#

I think that would be really cool to see!

tired yacht
#

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

livid briar
#

Honestly, I don't think you could go wrong with Python

tired yacht
#

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 meme

late magnet
tired yacht
#

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 meme

tired yacht
#

perhaps i am underestimating shitty jumper wires

#

⁉️

tired yacht
#

i'm sorry i doubted you shitty jumper wires

tired yacht
#

okay the problem with 150MHz is that my jtag protocol sucks

tired yacht
#

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

#

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

tired yacht
#

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

brisk orchid
#

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

tired yacht
#

? 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

brisk orchid
#

Oh I thought you're implementing an implementation for the ISA and not making an equivalent for the implementation, yeah that makes sense then

tired yacht
#

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)

brisk orchid
#

Ahh fair enough

tired yacht
#

hmm i was kinda thinking about the mmu, and i think i'm gonna redesign it to be hw refilled

tired yacht
#

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 meme

#

it's currently still limited to only one output section though, and i don't emit any phdrs, but it's still an improvement

tired yacht
#

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

tired yacht
#

dead project

tired yacht
#

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

tired yacht
#

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

tired yacht
#

i also quickly wired up the cursor position to CSRs so it can be moved by software

tired yacht
#

few updates since last time

tired yacht
tired yacht
#

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 meme (although that ofc will vary with PVT)

tired yacht
#

since the cdc was an unsynchronized crossing from the soc domain (at 100MHz) to the pixel domain (at 65MHz)

tired yacht
#

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

tired yacht
#

oh i was writing into the fault register on the hw side even if no fault happened

tired yacht
#

the mmu test:

  1. loads a identity mapping new page table and flushes the whole tlb
  2. maps VA 0x200000 to PA 0x800000 as RW, flushes the tlb entry for that VA
  3. writes something to 0x200000
  4. maps VA 0x200000 to PA 0x801000 as RW, flushes the tlb entry for that VA
  5. writes something to 0x200002
  6. prints the contents of 0x800000 and 0x801000
  7. unmaps VA 0x200000, flushes the tlb entry for that VA
  8. 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

tired yacht
#

thonk 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

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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

dark tide
#

what fpga is this running on? or is it the actual chip?

tired yacht
#

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%

tired yacht
#

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)

tired yacht
#

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

tired yacht
#

nice, reading from the flash also works

tired yacht
#

i get all the bits as expected, but shifted by one (and the rx fifo should only have 3 bytes instead of 4)

tired yacht
#

ah i see why

#

i just need to insert a one spi clock cycle delay for the transmit -> receive segment turnaround

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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

brisk orchid
#

Do you have a verification framework? It'd be difficult to catch all of the bugs via waveforms

tired yacht
#

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

brisk orchid
#

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

tired yacht
#

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 :^)

brisk orchid
#

Fair enough

tired yacht
#

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)

tired yacht
#

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 :^)

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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)

tired yacht
#

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

tired yacht
opaque furnace
#

nice!
now add a crc, revise the protocol slightly, and call it xmodem 😉

tired yacht
#

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

opaque furnace
#

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

tired yacht
#

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

opaque furnace
#

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

tired yacht
#

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)

tired yacht
#

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

opaque furnace
#

oh no lol!

tired yacht
#

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

tired yacht
#

140MHz with CL3 also works, hmm

tired yacht
#

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 :^)
tired yacht
#

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

tired yacht
#

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

tired yacht
tired yacht
#

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 meme

#

and all the traces are on the top layer so the bottom layer is just a full ground plane

tired yacht
#

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 meme

#

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)

tired yacht
#

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 meme

#

and in other news, the sipeed slogic16u3 is a very nice logic analyzer

tired yacht
#

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)

tired yacht
#

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

tired yacht
# tired yacht

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

tired yacht
#

hm one potential issue identified

gleaming citrus
#

nice how did I not know this was a thing

tired yacht
#
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)
tired yacht
tired yacht
#

color 243 is now fine, but 4 and 255 aren't :^)

tired yacht
#

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 :^)

tired yacht
#

hm, doesn't happen if i disable writing to the text framebuffer

tired yacht
#

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

tired yacht
#

i ended up fixing it btw

gleaming citrus
#

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?

tired yacht
#

with more focus on the former at the moment since im porting it to a new fpga board i got

gleaming citrus
#

nice, lots of breadboard?

tired yacht
#

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

hushed fox
#

ah i didn't realize you want to use a separate cpu

#

what will you do with the fpga once you have the cpu?

tired yacht
#

well the fpga is still the rest of the owl (all the peripherals, memory controller, etc)

gleaming citrus
#

the owl? rocky3

hushed fox
#

ah i see

tired yacht
#

it is perhaps a bit weird design wise but eh, i think its cool

hushed fox
#

do you use the discrete cpu to free up fpga space or is it just faster?

tired yacht
#

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 meme

tired yacht
#

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 meme

#

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)

tired yacht
# tired yacht final result, 256 colors :^)

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

tired yacht
#

i am not putting another one though, i need a better soldering setup

tired yacht
tired yacht
#

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

tired yacht
#

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 :^)

tired yacht
#

i also tried adding nops around the insns and they do execute and push back the point it fails at back

#

hmm

tired yacht
#

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)

tired yacht
#

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/

little tinsel
#

peam

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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

modest moat
#

65816 compooter 👀

tired yacht
#

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

tired yacht
#

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

tired yacht
# tired yacht

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

tired yacht
#

okay after a few hours of work (and sleep inbetween meme) 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

tired yacht
#

hmm running a test for a single instruction (with the cpu state setup and retrieval) takes like 9s meme

tired yacht
#

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

tired yacht
#

hmm the load immediate instructions also behave weird in emulation mode

#

this is very weird

tired yacht
#

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

tired yacht
#

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

tired yacht
#

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 meme

#

or i guess i can just take a random sample of tests from the suite :^)

tired yacht
#

okay well now we wait ig

tired yacht
tired yacht
#

after some more fixes for unexpected bugs (and forgetting to skip the halt opcodes from the test meme) 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

tired yacht
tired yacht
#

after 13 hours i have results:

All done!
Summary:  4671 successes out of 5040 tests (92.68%). 
modest moat
#

65832 port when trolland

tired yacht
tired yacht
modest moat
#

Ahhhh
Fair lol

modest moat
tired yacht
#

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)

modest moat
#

Ohhhh fair lol

#

Very based!!

tired yacht
#

ig now to go through the 369 failures and see what's up

tired yacht
#

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 meme

#

like, it needed proper sync signals, and proper control symbols meme

#

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)

tired yacht
#

another thing i got is a gigabit usb nic so i can experiment with ethernet

little tinsel
#

huge

hushed fox
tired yacht
#

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)

tired yacht
#

hmmm

#

i am wondering what i want to do now

#

thinking of either trying to get gmii working or working on the video subsystem

little tinsel
#

video

tired yacht
#

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 meme

tired yacht
tired yacht
tired yacht
#

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

tired yacht
#

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)

hushed fox
#

is the parser now in hw or sw?

tired yacht
#

hw