forked from arti/vt1200
One PIXBLT B,XY per source row paints the whole screen: DYDX names `stretch` rows and SPTCH = 0 makes each of them read the same packed source row, so the vertical stretch costs no extra CPU work. vram_b.asm carries the primitive (PSIZE=1, CONVSP/CONVDP=0x14, PMASK=0, OFFSET=0x04000000, DPTCH=0x800, raster op 0) plus DINT/EINT thunks; vidstream.c masks interrupts for the burst (spec 8.9), refuses geometry the screen cannot hold, and counts an OS repaint through a sentinel word in the unused right edge of the last screen row (OS_TOUCH, 0x01810220). MAME mode draw injects a 128-row frame (stretch 8 = 1024 screen rows, ruling R3) and dumps the screen; vidstream_check.py compares all 1024 rows against the bytes actually delivered. RED: 0/1024 rows matched. GREEN: 1024/1024, RX_OK=1, OS_TOUCH=0. Fixes a latent harness bug found on the way: `bpclear <address>` takes a breakpoint INDEX, so the idle-loop breakpoint was never cleared; once a burst masked interrupts long enough, its stale action fired again and re-entered main (a second malloc, counters back to 0).
139 lines
6.2 KiB
C
139 lines
6.2 KiB
C
/*
|
|
* frame_parse.h -- the wire format and the receive-side frame check.
|
|
*
|
|
* This is the one place the wire format is written down in C; the host sender
|
|
* writes it down in harness/udpstream/frame.py. The two must agree byte for
|
|
* byte, and harness/udpstream/tests/test_frame_parse.c pins the agreement with
|
|
* a frame generated by frame.py (a golden vector, not a value this code
|
|
* produced itself).
|
|
*
|
|
* offset size field
|
|
* ------ ---- ------------------------------------------------------
|
|
* 0 2 magic, ASCII 'V','S'
|
|
* 2 1 version, 0x01
|
|
* 3 1 kind (KIND_STRIP = 1, KIND_STATUS = 5)
|
|
* 4 2 frame_no, u16 little-endian
|
|
* 6 2 payload_len, u16 little-endian
|
|
* 8 1 rows
|
|
* 9 1 stretch
|
|
* 10 2 reserved, zero
|
|
* 12 2 crc16, u16 little-endian
|
|
* 14 2 padding, zero
|
|
*
|
|
* The CRC is CCITT-FALSE (polynomial 0x1021, initial value 0xFFFF, no input or
|
|
* output reflection, no final xor) over header bytes 0..11 followed by the
|
|
* payload -- never the CRC field itself.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* Marker page: bit 0x01810000, byte 0x00302000. A cell below is one 32-bit
|
|
* word. Cells are 0x20 BITS apart (4 bytes), which is the rule Task 3
|
|
* established: "marker named 0x018100NN" means bit 0x01810000 + NN*8, because
|
|
* the TMS34010 is bit-addressed and four bits after a word would still be
|
|
* inside the same word. The plan numbers the page from 0x01810100, so both
|
|
* offsets are given.
|
|
*
|
|
* cell page offset bit address meaning
|
|
* ------------ ----------- ----------- --------------------------------
|
|
* POOL_PTR +0x00 0x01810100 bit pointer to the 244 KB pool
|
|
* POOL_FREEB +0x20 0x01810120 heap free bytes before malloc
|
|
* POOL_FREEA +0x40 0x01810140 heap free bytes after malloc
|
|
* VID_ALIVE +0x60 0x01810160 heartbeat while the loop runs
|
|
* RX_OK +0x80 0x01810180 frames accepted by fp_parse
|
|
* RX_BAD +0xA0 0x018101A0 frames rejected by fp_parse
|
|
* RX_LAST_LEN +0xC0 0x018101C0 payload_len of the last accepted
|
|
* RX_SLOT +0xE0 0x018101E0 slot of the last accepted (0=A)
|
|
* OS_TOUCH +0x220 0x01810220 times the VRAM canary was not ours
|
|
*
|
|
* OS_TOUCH is Task 5's OS-repaint canary (design spec 8.9): the blit leaves one
|
|
* 16-bit sentinel word in the unused right edge of the last screen row, and
|
|
* OS_TOUCH counts how often that word was not what the payload left there. It
|
|
* is at cell 0x44 -- free between the RX_* counters and the injection cells
|
|
* below (ruling R12).
|
|
*
|
|
* Debugger injection interface (Task 4; the harness plays the receive path):
|
|
*
|
|
* RX_SLOT_A +0x200 0x01810300 bit address of slot A
|
|
* RX_SLOT_B +0x220 0x01810320 bit address of slot B
|
|
* RX_READY_A +0x240 0x01810340 1 = slot A holds RX_LEN_A bytes
|
|
* RX_LEN_A +0x260 0x01810360 bytes in slot A
|
|
* RX_READY_B +0x280 0x01810380 1 = slot B holds RX_LEN_B bytes
|
|
* RX_LEN_B +0x2A0 0x018103A0 bytes in slot B
|
|
*
|
|
* The RX_READY_x and RX_LEN_x cells are NOT part of the wire protocol. In the real
|
|
* transport the LANCE receive interrupt fills a slot and raises that slot's
|
|
* ready flag; MAME cannot hand this emulated LANCE an Ethernet frame (the
|
|
* driver attaches no network interface -- ruling R11), so the debugger writes
|
|
* the slot and raises the flag instead. The payload clears it.
|
|
* ---------------------------------------------------------------------------
|
|
*
|
|
* K&R C only. The 1987 toolchain in build-stream.sh compiles this header, so
|
|
* there are no ANSI prototypes, no '//' comments and no 'const' (romapi.h is
|
|
* written the same way): the parameter lists live in the comments and in the
|
|
* definitions in frame_parse.c.
|
|
*
|
|
* 8.3 names. The 1987 preprocessor cannot open an 11-character base name
|
|
* (Task 3 hit this with VIDSTREAM.C), so this file is also reachable as
|
|
* frmpsr.h -- a symlink to it -- and build-stream.sh stages it as FRMPSR.H.
|
|
* Host compiles may use either name.
|
|
*/
|
|
#ifndef FRAME_PARSE_H
|
|
#define FRAME_PARSE_H
|
|
|
|
#define FP_HEADER_SIZE 16 /* bytes of fixed header before the payload */
|
|
#define FP_PREFIX_SIZE 12 /* header bytes the CRC covers, before payload */
|
|
#define FP_CRC_OFF 12 /* offset of the stored crc16, low byte first */
|
|
|
|
#define FP_MAGIC0 0x56 /* 'V' */
|
|
#define FP_MAGIC1 0x53 /* 'S' */
|
|
#define FP_VERSION 1
|
|
|
|
#define FP_KIND_STRIP 1
|
|
#define FP_KIND_STATUS 5
|
|
|
|
/* Marker page cells; see the table above. */
|
|
#define MARK_PAGE 0x01810000
|
|
#define MARK_CELL(n) (MARK_PAGE + (n) * 8)
|
|
|
|
#define POOL_PTR MARK_CELL(0x20)
|
|
#define POOL_FREEB MARK_CELL(0x24)
|
|
#define POOL_FREEA MARK_CELL(0x28)
|
|
#define VID_ALIVE MARK_CELL(0x2c)
|
|
|
|
#define RX_OK MARK_CELL(0x30)
|
|
#define RX_BAD MARK_CELL(0x34)
|
|
#define RX_LAST_LEN MARK_CELL(0x38)
|
|
#define RX_SLOT MARK_CELL(0x3c)
|
|
|
|
#define OS_TOUCH MARK_CELL(0x44)
|
|
|
|
#define RX_SLOT_A MARK_CELL(0x60)
|
|
#define RX_SLOT_B MARK_CELL(0x64)
|
|
#define RX_READY_A MARK_CELL(0x68)
|
|
#define RX_LEN_A MARK_CELL(0x6c)
|
|
#define RX_READY_B MARK_CELL(0x70)
|
|
#define RX_LEN_B MARK_CELL(0x74)
|
|
|
|
/* What a valid frame told us. payload_off is FP_HEADER_SIZE on success. */
|
|
typedef struct {
|
|
unsigned int kind;
|
|
unsigned int frame_no;
|
|
unsigned int rows;
|
|
unsigned int stretch;
|
|
unsigned int payload_len;
|
|
unsigned int payload_off;
|
|
} fp_header;
|
|
|
|
/*
|
|
* int fp_parse(unsigned char *buf, unsigned int len, fp_header *out)
|
|
*
|
|
* 1 and *out filled when buf[0..len) is a well-formed frame, 0 otherwise.
|
|
* A frame is well formed when len is at least FP_HEADER_SIZE, the magic and
|
|
* version match, len is at least FP_HEADER_SIZE + payload_len, and the stored
|
|
* crc16 equals the CCITT-FALSE CRC over buf[0..11] and the payload. Bytes
|
|
* beyond the frame are ignored: payload_len is authoritative (the transport
|
|
* hands us exactly what arrived, and a tail is not part of the frame).
|
|
*/
|
|
int fp_parse();
|
|
|
|
#endif /* FRAME_PARSE_H */
|