#!/usr/bin/env python3 """What AUTO-REQUEST DMA costs the 68000, when there is no request line. python3 tools/analysis/28_autorequest_cost.py --kbps 460 [--record 37405] WHY THIS EXISTS. The project's per-byte ladder -- W = 5 single-address held, 9 dual held, 12 single arbitrated, 16..19 dual arbitrated (FINDINGS 42.4, 52.5) -- prices a transfer that the DEVICE asks for: one external request, one operand, a known number of stolen clocks per delivered byte. Session 27 found that the CZ-6BS1 as MAME models it has NO REQUEST LINE to the DMAC at all (FINDINGS 59.2): the card's flow control is DTACK, and every configuration that can be run against it is AUTO-REQUEST, where the channel transfers because its own counter says so and not because a byte has arrived. THAT CHANGES THE CURRENCY, and it is the reason this file is not a line in another one. An externally requested transfer is charged PER DELIVERED BYTE. An auto-requested one is charged PER UNIT OF TIME THE CHANNEL IS ACTIVE, because the channel has no way to know the device is not ready: it takes its allotted share of the bus and spends it whether or not a byte comes back. So the cost of delivering a record depends on HOW LONG THE RECORD TAKES TO ARRIVE -- i.e. on the delivery rate, the figure this tree deliberately has no default for (FINDINGS 50) -- and the tool REQUIRES one rather than assuming it. SOURCED: MC68450 Direct Memory Access Controller, Motorola, Jul 1989 (bitsavers), sections 3.8 and 5.2.3.3, the same document buscost.py's transfer timings come from. Section 5.2.3.3.1: under maximum-rate auto-request "all operands in the data block will be transferred in one burst, so that the DMAC will use 100% of the available bus bandwidth" -- which is the datasheet saying, in its own words, what session 27 measured MAME's model doing when it HALTED the 68000 for the whole data phase (FINDINGS 59.1). THE ONE LOAD-BEARING ASSUMPTION, stated because the whole table rests on it: that the channel SPENDS its allotted share whether or not the device has a byte. Under auto-request a request is pending until MTC is exhausted, so the DMAC takes the bus during every burst window it is entitled to; when the device is not ready the cycle is stretched by wait states (a real CZ-6BS1 negating DTACK) or retried later (MAME's model discards the operand), and either way the window is gone from the CPU's point of view. If a real card instead lets the DMAC off the bus early when no byte is there, these figures are UPPER BOUNDS. That is a board question and it is ROADMAP B3's. NOT A MEASUREMENT. Every figure below is arithmetic over datasheet constants and an explicit rate. `W` is still unmeasured and still wants a board. """ import sys, os, argparse sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from buscost import DMA_DUAL_BYTE_CLK, DMA_READ_CLK, DMA_WRITE_CLK CPU_HZ = 10_000_000.0 # the X68000 the whole tree is costed against FPS = 12.0 ap = argparse.ArgumentParser() ap.add_argument("--kbps", type=float, required=True, help="delivery rate in KB/s. REQUIRED: this tree has no default " "rate and the whole answer scales with it (FINDINGS 50).") ap.add_argument("--record", type=int, default=37405, help="mean record size in bytes (default: the gate container's)") a = ap.parse_args() RATE = a.kbps * 1024.0 # --- 3.8 GENERAL CONTROL REGISTER, decoded from the formulas in 5.2.3.3.2 --- # burst time = 2^(BT + 4) clocks # sample period = 2^(BT + BR + 5) clocks # DMAC's share = 2^-(BR + 1) # and Table 5-3 prints all sixteen combinations, so the formulas are GATED # against the table rather than trusted. TABLE = { # (BR, BT): (burst, MPU period, share, sample period) (0,0):(16,16,.5,32), (0,1):(32,32,.5,64), (0,2):(64,64,.5,128), (0,3):(128,128,.5,256), (1,0):(16,48,.25,64), (1,1):(32,96,.25,128), (1,2):(64,192,.25,256),(1,3):(128,384,.25,512), (2,0):(16,112,.125,128),(2,1):(32,224,.125,256),(2,2):(64,448,.125,512),(2,3):(128,896,.125,1024), (3,0):(16,240,.0625,256),(3,1):(32,480,.0625,512),(3,2):(64,960,.0625,1024), (3,3):(128,1920,.0625,2048), } bad = 0 for (br, bt), (burst, mpu, share, sample) in sorted(TABLE.items()): f_burst, f_sample, f_share = 2**(bt+4), 2**(bt+br+5), 2.0**-(br+1) for got, want, what in ((f_burst, burst, "burst time"), (f_sample, sample, "sample period"), (f_share, share, "bandwidth share"), (f_sample - f_burst, mpu, "MPU period")): if got != want: print(f" FAIL BR={br:02b} BT={bt:02b} {what}: formula {got}, " f"Table 5-3 {want}") bad += 1 if bad: sys.exit(f"\n{bad} disagreements between 5.2.3.3.2's formulas and Table 5-3. " "Everything below\nis those formulas, so it is not printed.") print(f"MC68450 5.2.3.3.2's formulas reproduce all 16 rows of Table 5-3.\n") BYTE_CLK = DMA_DUAL_BYTE_CLK # dual address, 8-bit port: a 4-clock read of # $EA0015 and a 5-clock write to the ring frame_clk = CPU_HZ / FPS wire_s = a.record / RATE # how long the record takes to land wire_clk = wire_s * CPU_HZ # ...in 68000 clocks per_byte_wire = wire_clk / a.record # clocks of wall time per byte print(f"THE RECORD: {a.record:,} B at {a.kbps:g} KB/s = {wire_s*1000:.2f} ms " f"= {wire_clk:,.0f} clocks = {100*wire_clk/frame_clk:.1f}% of a " f"{FPS:g} fps frame") print(f" one byte of WIRE TIME is {per_byte_wire:.2f} clocks; one byte of DMAC " f"WORK is {BYTE_CLK} ({DMA_READ_CLK} read + {DMA_WRITE_CLK} write, " f"buscost.py)\n") print("REQG 01, AUTO-REQUEST AT MAXIMUM RATE -- what session 27 demonstrated") print(f" The channel holds the bus until MTC is exhausted (5.2.3.3.1: 100% of " f"the\n bandwidth), so the CPU gets NOTHING for the whole delivery:") print(f" cost to the 68000 = the whole {100*wire_clk/frame_clk:.1f}% of a " f"frame, or {per_byte_wire:.2f} clk/B") print(f" It is the cheapest configuration per BYTE MOVED and the dearest per " f"byte\n DELIVERED, and the gap between those is the device's own " f"slowness:\n {BYTE_CLK} clocks of work in {per_byte_wire:.1f} clocks " f"of waiting = {100*BYTE_CLK/per_byte_wire:.1f}% of the held bus does " f"anything.\n") print("REQG 00, LIMITED-RATE AUTO-REQUEST -- the lever the GCR actually gives") print(" The DMAC takes its programmed share of the bus and spends it whether " "or not\n a byte is there, so the CPU pays the SHARE for the WHOLE " "delivery -- and the\n share must also be big enough to carry the rate. " "Both, or it does not fit.\n") print(" BR share sustains clk/B charged % of a frame fits " f"{a.kbps:g} KB/s?") fits_any = [] for br in range(4): burst, mpu, share, sample = TABLE[(br, 3)] # BT=11, the longest burst # bytes the channel can move inside one burst window, and how often that # window comes round bytes_per_burst = burst // BYTE_CLK sustains = bytes_per_burst * CPU_HZ / sample charged = share * per_byte_wire # clocks the CPU loses per # DELIVERED byte pct = 100 * share * wire_clk / frame_clk ok = sustains >= RATE if ok: fits_any.append((br, share, charged, pct)) print(f" {br:02b} {share*100:5.2f}% {sustains/1024:7.1f} KB/s " f"{charged:9.2f} {pct:8.1f}% {'yes' if ok else 'NO'}") print(f"\n (BT = 11 throughout: the longest burst, 128 clocks, which is the " f"most\n favourable row -- a shorter burst moves fewer bytes per window " f"at the same\n share and sustains proportionally less.)") if not fits_any: print(f"\n NOTHING FITS. At {a.kbps:g} KB/s no limited-rate share can " f"carry the record,\n so the only auto-request configuration that " f"delivers is maximum rate --\n and that one stops the CPU for the " f"whole {100*wire_clk/frame_clk:.1f}% of a frame the record takes.") else: br, share, charged, pct = fits_any[0] print(f"\n CHEAPEST THAT FITS: BR = {br:02b}, {share*100:g}% of the bus, " f"{charged:.2f} clk/B charged to the\n 68000 -- {pct:.1f}% of a frame " f"per record.") print(f" Against the ladder: W=5 held costs {5*a.record/frame_clk*100:.1f}%, " f"W=9 dual held {9*a.record/frame_clk*100:.1f}%,\n W=19 the IPL ROM's " f"own {19*a.record/frame_clk*100:.1f}%, and PIO measured " f"{87.28*a.record/frame_clk*100:.1f}% (FINDINGS 58.2).") print(f""" WHAT THIS SETTLES, AND WHAT IT DOES NOT 1. AUTO-REQUEST IS CHARGED BY TIME, NOT BY BYTE. Every W in this project is clocks per DELIVERED byte, which presumes the device asks. With no request line the channel spends its share of the bus at a rate it was told, so the record's cost scales with how long the disc takes -- halve the delivery rate and the CPU cost of the same record DOUBLES. No W does that. 2. THE GCR IS A DESIGN LEVER NOBODY HAD NAMED. BT and BR are two bits each and they set what fraction of the bus the player gives away. That is the same kind of choice as `aligned` vs `split` and it belongs in the same list. 3. IT IS STILL NOT A MEASUREMENT. These are datasheet constants and an explicit rate. Whether the real CZ-6BS1 drives #EXREQ (pin B36 exists on the slot, and MAME's model simply does not connect it) is ROADMAP B3's question, and if it does, the ladder applies and this file is the fallback rather than the plan.""")