// ============================================================================ // mgpr832_tb.v — Testbench for the mgpr832 register card // // Verifies, against the structural model generated from mgpr832.net: // // 1. Write/read round trip for all 8 registers on both read ports, with a // distinct pattern per register, checked after every register has been // written so a read cannot pass by reading data still in flight. // 2. Bit fidelity: walking-1 and walking-0 across all 32 bits. The card // permutes data pins twice between the write buffers and the read buses // (routing convenience on the PCB); these sweeps are what proves the two // permutations actually cancel, bit for bit, rather than merely looking // symmetric in the netlist. // 3. Write isolation: rewriting one register disturbs none of the other 7. // 4. Independent read ports: A and B reading different registers at once. // 5. 3-state: Ab/Bb float when that port's read enable is deasserted, and a // port is unaffected by the other port's enable. // 6. Write inhibit: with /WR HIGH no register changes, whatever YSEL does. // 7. Latch transparency: while /WR is LOW the addressed register follows Y // through to the read port, and the data-hold window after /WR rises // (the card is level-sensitive, not edge-triggered). // 8. Bank select in both SEL3 polarities, including that a deselected card // RELEASES Ab/Bb rather than driving them, and a two-card pair sharing // one set of read buses: the handoff is measured for driver overlap. // 9. End-to-end propagation delays against the sum of the data sheet typ // values along each path, to 0.11 ns, the same tolerance the individual // device testbenches use. // // Delays are the '240/'244/'373/'138 typ values from ../74FAST/src, so this // only means anything when compiled with -gspecify (see the Makefile). // ============================================================================ `timescale 1ns/100ps module mgpr832_tb; reg [3:0] asel, bsel, ysel; reg rea_n, reb_n, wr_n; reg [31:0] y; wire [31:0] ab, bb; mgpr832 dut ( .asel(asel), .bsel(bsel), .ysel(ysel), .rea_n(rea_n), .reb_n(reb_n), .wr_n(wr_n), .y(y), .ab(ab), .bb(bb) ); integer errors = 0; // A second pair of cards, strapped as the two halves of one 16-register // space and sharing a single set of read buses -- the arrangement the bank // gate exists for. They see the same stimulus as dut. Nothing terminates // ab_pair/bb_pair here: the card's terminators sit on its internal buses, // not on Ab/Bb, so leaving these free lets section 8 tell a handoff gap // (all-Z) apart from a driver overlap (X). wire [31:0] ab_pair, bb_pair; mgpr832 #(.JP1_SEL3(1'b0), .JP2_SEL3(1'b0), .JP3_SEL3(1'b0)) card_lo ( .asel(asel), .bsel(bsel), .ysel(ysel), .rea_n(rea_n), .reb_n(reb_n), .wr_n(wr_n), .y(y), .ab(ab_pair), .bb(bb_pair) ); mgpr832 #(.JP1_SEL3(1'b1), .JP2_SEL3(1'b1), .JP3_SEL3(1'b1)) card_hi ( .asel(asel), .bsel(bsel), .ysel(ysel), .rea_n(rea_n), .reb_n(reb_n), .wr_n(wr_n), .y(y), .ab(ab_pair), .bb(bb_pair) ); // ------------------------------------------------------------------ // Expected path delays: the sum of the data sheet typ values of every // device on the path. U103/U104 buffer the control inputs, U117 ('86) // straps the SEL3 polarity, U119 ('02) qualifies it with the read strobe, // U118 ('240) inverts and buffers that out to the output drivers, U88..U90 // decode, U73/U74 buffer the read strobes, U75 inverts the write strobes, // U107.. buffer Y, the per-register '240s drive the internal A/B bus and // U105.. re-invert it onto Ab/Bb. // ------------------------------------------------------------------ localparam real F244_D = 6.0; // '244 data to output localparam real F240_D = 4.5; // '240 data to output localparam real F240_ZL = 10.0, F240_HZ = 10.0; // '240 enable/disable localparam real F138_AH = 6.1; // '138 address, tPHL localparam real F138_E3H = 5.6; // '138 E3, tPHL localparam real F373_DL = 4.3; // '373 D to O, tPLH localparam real F02_LH = 3.5, F02_HL = 2.6; // '02 NOR // '86 delay depends on the state of the other input, i.e. on the strap. // These are the strap-LOW arcs, which is how dut and card_lo are set. localparam real F86_LH = 4.0, F86_HL = 4.2; // /REA LOW -> Ab valid. The slow leg turns the addressed register's read // buffers on; the output drivers turn on ~11 ns before that, so the bank // gate stays off the critical path. localparam real T_READ_EN = F244_D + F02_LH + F138_E3H + F244_D + F240_ZL + F240_D; // /REA HIGH -> Ab in 3-state: only the output drivers matter, but they now // hang off the bank gate rather than off /REAb directly. localparam real T_READ_DIS = F244_D + F02_HL + F240_D + F240_HZ; // ASEL -> Ab valid, with /REA already LOW. The bank gate is not in the // address path, so this is unchanged by the rework. localparam real T_SELECT = F244_D + F138_AH + F244_D + F240_ZL + F240_D; // Y -> Ab with the addressed register transparent (/WR LOW) and read. localparam real T_WRITE_TH = F244_D + F373_DL + F240_D + F240_D; // SEL3 -> Ab, i.e. a card being selected or released by the bank gate. localparam real T_BANK_EN = F244_D + F86_HL + F02_LH + F138_E3H + F244_D + F240_ZL + F240_D; localparam real T_BANK_DIS = F244_D + F86_LH + F02_HL + F240_D + F240_HZ; localparam real TOL = 0.11; // How much driver overlap to tolerate on a shared read bus during a bank // handoff. Any overlap at all is two FAST drivers fighting; a few hundred // ps is survivable, anything approaching a nanosecond is a design problem. localparam real CONTENTION_MAX = 1.0; // Settling times used by the stimulus tasks: comfortably past the slowest // path above, and past the ~16 ns it takes /WR to reach the latches. localparam SETTLE = 60; // after any change, before sampling localparam WR_PULSE = 60; // /WR LOW time localparam WR_HOLD = 40; // Y held after /WR rises, for the latch to close // ------------------------------------------------------------------ // Checks // ------------------------------------------------------------------ task check32(input [31:0] got, input [31:0] exp, input [511:0] what); begin if (got !== exp) begin errors = errors + 1; $display("ERROR: %0s: got %08h, expected %08h", what, got, exp); end end endtask task check_z32(input [31:0] got, input [511:0] what); begin if (got !== 32'hzzzz_zzzz) begin errors = errors + 1; $display("ERROR: %0s: got %08h, expected all high-Z", what, got); end end endtask // Like tb_common's check_delay, but takes an already-measured interval: // these paths are timed by watching when a 32-bit bus last changed, not by // waiting on one edge. (See tb_common.vh's note on tb/f182_tb.v, the other // testbench that supplies its own.) `define TB_NO_SHARED_CHECK_DELAY task check_delay(input real dt, input real dmin, input real dmax, input [511:0] what); begin if (dt < dmin - 0.01 || dt > dmax + 0.01) begin errors = errors + 1; $display("ERROR: %0s: delay %0.2f ns outside [%0.2f, %0.2f]", what, dt, dmin, dmax); end end endtask `include "tb_common.vh" initial watchdog("mgpr832"); // `make wave` passes +vcd=; without it nothing is dumped, so the // ordinary run stays fast and leaves no stray files. reg [1023:0] vcd_path; initial begin if ($value$plusargs("vcd=%s", vcd_path)) begin $dumpfile(vcd_path); $dumpvars(0, mgpr832_tb); end end // Last-transition timestamps, for the delay checks below. real ab_changed, bb_changed; always @(ab) ab_changed = $realtime; always @(bb) bb_changed = $realtime; // ------------------------------------------------------------------ // Shared-bus monitor for the two-card handoff in section 8. // // On ab_pair, X means two cards driving the same bit to opposite levels // and Z means neither is driving. Both are transient during a bank switch; // what matters is that the overlap is short (or absent) and that the gap // is a gap and not a fight. `=== 1'bx` distinguishes contention from the // high-Z a bare === comparison would lump in with it. // ------------------------------------------------------------------ reg bus_watch = 1'b0; reg x_active = 1'b0, z_active = 1'b0; real x_t0, z_t0; real x_worst = 0.0, z_worst = 0.0; function has_x(input [31:0] v); integer k; begin has_x = 1'b0; for (k = 0; k < 32; k = k + 1) if (v[k] === 1'bx) has_x = 1'b1; end endfunction always @(ab_pair) if (bus_watch) begin if (has_x(ab_pair)) begin if (!x_active) begin x_active = 1'b1; x_t0 = $realtime; end end else if (x_active) begin x_active = 1'b0; if ($realtime - x_t0 > x_worst) x_worst = $realtime - x_t0; end if (ab_pair === 32'hzzzz_zzzz) begin if (!z_active) begin z_active = 1'b1; z_t0 = $realtime; end end else if (z_active) begin z_active = 1'b0; if ($realtime - z_t0 > z_worst) z_worst = $realtime - z_t0; end end // ------------------------------------------------------------------ // Stimulus // ------------------------------------------------------------------ // A write is a level-sensitive pulse: the addressed register is // transparent while /WR is LOW and captures when it rises, so Y must stay // valid until the latches close. task write_reg(input [3:0] sel, input [31:0] value); begin ysel = sel; y = value; #SETTLE; wr_n = 1'b0; #WR_PULSE; wr_n = 1'b1; #WR_HOLD; end endtask task read_a(input [3:0] sel); begin asel = sel; rea_n = 1'b0; #SETTLE; end endtask task read_b(input [3:0] sel); begin bsel = sel; reb_n = 1'b0; #SETTLE; end endtask // The 8 register patterns: every byte lane distinct, and between them // every bit takes both values. function [31:0] pattern(input integer n); pattern = {8'h10 + n[7:0], 8'h5A ^ (n[7:0] << 4), 8'hF0 - n[7:0], 8'hA5 ^ n[7:0]}; endfunction integer i, b; real t0; initial begin asel = 4'd0; bsel = 4'd0; ysel = 4'd0; rea_n = 1'b1; reb_n = 1'b1; wr_n = 1'b1; y = 32'h0000_0000; #100; // -------------------------------------------------------------- // 1. Write every register, then read every register on both ports. // All 8 are written before any is read, so a passing read cannot // be data still sitting on the internal bus from its own write. // -------------------------------------------------------------- for (i = 0; i < 8; i = i + 1) write_reg(i[3:0], pattern(i)); for (i = 0; i < 8; i = i + 1) begin read_a(i[3:0]); check32(ab, pattern(i), "port A read back"); read_b(i[3:0]); check32(bb, pattern(i), "port B read back"); end // -------------------------------------------------------------- // 2. Walking 1 and walking 0 through register 3: the data pins are // permuted twice on the way from Y to Ab, so this is the check // that the permutations cancel bit for bit. // -------------------------------------------------------------- for (b = 0; b < 32; b = b + 1) begin write_reg(4'd3, 32'h1 << b); read_a(4'd3); check32(ab, 32'h1 << b, "walking 1, port A"); read_b(4'd3); check32(bb, 32'h1 << b, "walking 1, port B"); end for (b = 0; b < 32; b = b + 1) begin write_reg(4'd3, ~(32'h1 << b)); read_a(4'd3); check32(ab, ~(32'h1 << b), "walking 0, port A"); read_b(4'd3); check32(bb, ~(32'h1 << b), "walking 0, port B"); end // -------------------------------------------------------------- // 3. Write isolation: restore register 3 and confirm the other // seven still hold what section 1 put there. // -------------------------------------------------------------- write_reg(4'd3, pattern(3)); for (i = 0; i < 8; i = i + 1) begin read_a(i[3:0]); check32(ab, pattern(i), "undisturbed by writes to register 3"); end // -------------------------------------------------------------- // 4. The two read ports are independent: different registers at the // same time, and each unaffected by the other's enable. // -------------------------------------------------------------- for (i = 0; i < 8; i = i + 1) begin asel = i[3:0]; bsel = 7 - i[3:0]; rea_n = 1'b0; reb_n = 1'b0; #SETTLE; check32(ab, pattern(i), "port A, ports reading different regs"); check32(bb, pattern(7 - i), "port B, ports reading different regs"); end // -------------------------------------------------------------- // 5. 3-state behaviour. // -------------------------------------------------------------- rea_n = 1'b1; reb_n = 1'b0; asel = 4'd2; bsel = 4'd5; #SETTLE; check_z32(ab, "port A disabled"); check32(bb, pattern(5), "port B still driven while A is disabled"); rea_n = 1'b0; reb_n = 1'b1; #SETTLE; check32(ab, pattern(2), "port A driven again while B is disabled"); check_z32(bb, "port B disabled"); rea_n = 1'b1; #SETTLE; check_z32(ab, "both ports disabled, A"); check_z32(bb, "both ports disabled, B"); // -------------------------------------------------------------- // 6. Write inhibit: /WR HIGH means no register changes, however YSEL // and Y move. // -------------------------------------------------------------- for (i = 0; i < 8; i = i + 1) begin ysel = i[3:0]; y = 32'hDEAD_BEEF; #SETTLE; end for (i = 0; i < 8; i = i + 1) begin read_a(i[3:0]); check32(ab, pattern(i), "unchanged with /WR HIGH"); end rea_n = 1'b1; #SETTLE; // -------------------------------------------------------------- // 7. Latch transparency and the data-hold window. // // /WR reaches a latch's LE about 16 ns after the card input, so // while /WR is LOW the addressed register follows Y straight // through to the read port, and Y must stay valid for roughly // 16 - 6 = 10 ns after /WR rises (6 ns being the '244 that buffers // Y). Both sides of that boundary are checked. // -------------------------------------------------------------- ysel = 4'd6; y = 32'h0000_0000; asel = 4'd6; rea_n = 1'b0; #SETTLE; wr_n = 1'b0; #SETTLE; check32(ab, 32'h0000_0000, "transparent: register follows Y"); y = 32'hFFFF_FFFF; #SETTLE; check32(ab, 32'hFFFF_FFFF, "transparent: register follows Y changing"); y = 32'h1234_5678; #SETTLE; check32(ab, 32'h1234_5678, "transparent: register follows Y again"); wr_n = 1'b1; #WR_HOLD; check32(ab, 32'h1234_5678, "captured on the trailing edge of /WR"); // Y changing 5 ns after /WR rises still reaches the latch in time. wr_n = 1'b0; #SETTLE; y = 32'hAAAA_AAAA; #SETTLE; wr_n = 1'b1; #5 y = 32'h5555_5555; #WR_HOLD; check32(ab, 32'h5555_5555, "Y change 5 ns after /WR rises is captured"); // 15 ns after, it does not: the latch has closed. wr_n = 1'b0; #SETTLE; y = 32'hAAAA_AAAA; #SETTLE; wr_n = 1'b1; #15 y = 32'h5555_5555; #WR_HOLD; check32(ab, 32'hAAAA_AAAA, "Y change 15 ns after /WR rises is too late"); write_reg(4'd6, pattern(6)); // restore rea_n = 1'b1; #SETTLE; // -------------------------------------------------------------- // 8. Bank select, on the two cards sharing ab_pair/bb_pair. card_lo // is strapped to answer when SEL3 is LOW, card_hi when it is HIGH, // so between them they cover a 16-register space over one set of // read buses. // // The point of the bank gate is that a deselected card RELEASES the // read buses. Its '138s are held off at E3 as well, so its internal // buses stay quiet rather than switching on every address change. // -------------------------------------------------------------- write_reg(4'd0, 32'hC0DE_0001); // YSEL3 LOW: card_lo writes write_reg(4'd8, 32'hC0DE_0002); // YSEL3 HIGH: card_hi writes asel = 4'd0; bsel = 4'd0; rea_n = 1'b0; reb_n = 1'b0; #SETTLE; check32(ab_pair, 32'hC0DE_0001, "bank LOW selected, port A"); check32(bb_pair, 32'hC0DE_0001, "bank LOW selected, port B"); asel = 4'd8; bsel = 4'd8; #SETTLE; check32(ab_pair, 32'hC0DE_0002, "bank HIGH selected, port A"); check32(bb_pair, 32'hC0DE_0002, "bank HIGH selected, port B"); // Each card must have ignored the other bank's write: if JP3 were not // gating the write decoder, both would hold whichever value came last. asel = 4'd0; bsel = 4'd0; #SETTLE; check32(ab_pair, 32'hC0DE_0001, "write to the other bank was ignored"); // With neither bank addressed there is nothing to select, but /REA is // still asserted -- this is the case the old design got wrong, where // both cards drove the bus LOW instead of letting go of it. rea_n = 1'b1; reb_n = 1'b1; #SETTLE; check_z32(ab_pair, "both cards deselected: port A released"); check_z32(bb_pair, "both cards deselected: port B released"); // Bank handoff with /REA held LOW throughout: the outgoing card must // release before the incoming one drives. Swept both directions, // because the strap sets which '86 arc each card takes and the two // directions are not symmetric. rea_n = 1'b0; reb_n = 1'b0; asel = 4'd0; bsel = 4'd0; #SETTLE; bus_watch = 1'b1; // only the handoffs, so that the // gap measured is a handoff gap // and not a deliberate release for (i = 0; i < 4; i = i + 1) begin asel = 4'd8; bsel = 4'd8; #SETTLE; check32(ab_pair, 32'hC0DE_0002, "handoff LOW->HIGH settles"); asel = 4'd0; bsel = 4'd0; #SETTLE; check32(ab_pair, 32'hC0DE_0001, "handoff HIGH->LOW settles"); end bus_watch = 1'b0; if (x_worst > CONTENTION_MAX) begin errors = errors + 1; $display("ERROR: bank handoff: %0.2f ns of driver overlap, limit %0.2f", x_worst, CONTENTION_MAX); end $display("INFO: bank handoff: worst overlap %0.2f ns, worst gap %0.2f ns", x_worst, z_worst); rea_n = 1'b1; reb_n = 1'b1; #SETTLE; // The single-card dut is strapped to answer when SEL3 is LOW, so a // read at SEL3 HIGH must leave its buses released too. asel = 4'd8; rea_n = 1'b0; #SETTLE; check_z32(ab, "single card, SEL3 mismatched: port A released"); rea_n = 1'b1; #SETTLE; // -------------------------------------------------------------- // 9. Propagation delays. // -------------------------------------------------------------- write_reg(4'd0, 32'h0000_0000); write_reg(4'd1, 32'hFFFF_FFFF); // /REA LOW -> Ab valid. Register 1 is all ones, the slow direction: // the output drivers turn on first and present the terminated (HIGH) // internal bus inverted, i.e. all zeros, before the register's own // buffers pull the bus down. asel = 4'd1; rea_n = 1'b1; #SETTLE; t0 = $realtime; rea_n = 1'b0; #SETTLE; check32(ab, 32'hFFFF_FFFF, "delay run: /REA enable value"); check_delay(ab_changed - t0, T_READ_EN - TOL, T_READ_EN + TOL, "/REA LOW to Ab valid"); // ASEL -> Ab valid, /REA already LOW: register 1 (all ones) to // register 0 (all zeros) and back, the second direction being the // slow one for the same reason. asel = 4'd0; #SETTLE; check32(ab, 32'h0000_0000, "delay run: select register 0"); t0 = $realtime; asel = 4'd1; #SETTLE; check32(ab, 32'hFFFF_FFFF, "delay run: select register 1"); check_delay(ab_changed - t0, T_SELECT - TOL, T_SELECT + TOL, "ASEL to Ab valid"); // /REA HIGH -> Ab released. t0 = $realtime; rea_n = 1'b1; #SETTLE; check_z32(ab, "delay run: /REA disable"); check_delay(ab_changed - t0, T_READ_DIS - TOL, T_READ_DIS + TOL, "/REA HIGH to Ab high-Z"); // Y -> Ab straight through a transparent register. ysel = 4'd1; asel = 4'd1; y = 32'h0000_0000; rea_n = 1'b0; #SETTLE; wr_n = 1'b0; #SETTLE; check32(ab, 32'h0000_0000, "delay run: transparent, Y low"); t0 = $realtime; y = 32'hFFFF_FFFF; #SETTLE; check32(ab, 32'hFFFF_FFFF, "delay run: transparent, Y high"); check_delay(ab_changed - t0, T_WRITE_TH - TOL, T_WRITE_TH + TOL, "Y to Ab through a transparent register"); wr_n = 1'b1; #WR_HOLD; // SEL3 -> Ab, the bank gate's own paths, with /REA held LOW. Selecting // runs the full chain through the '138 to the register buffers; // releasing only has to reach the output drivers, so it is far shorter // -- which is what keeps a two-card handoff from overlapping. asel = 4'd1; rea_n = 1'b0; #SETTLE; check32(ab, 32'hFFFF_FFFF, "delay run: bank selected"); t0 = $realtime; asel = 4'd9; // same register, other bank #SETTLE; check_z32(ab, "delay run: bank released"); check_delay(ab_changed - t0, T_BANK_DIS - TOL, T_BANK_DIS + TOL, "SEL3 deselect to Ab high-Z"); t0 = $realtime; asel = 4'd1; #SETTLE; check32(ab, 32'hFFFF_FFFF, "delay run: bank selected again"); check_delay(ab_changed - t0, T_BANK_EN - TOL, T_BANK_EN + TOL, "SEL3 select to Ab valid"); rea_n = 1'b1; #SETTLE; finish_test("mgpr832"); end endmodule