diff --git a/hardware/fpga/bbu/Makefile b/hardware/fpga/bbu/Makefile new file mode 100644 index 0000000..a194573 --- /dev/null +++ b/hardware/fpga/bbu/Makefile @@ -0,0 +1,19 @@ +TARGETS = bbu.vvp test_stdlogic.vvp test_mac128pal.vvp + +all: $(TARGETS) + +.SUFFIXES: .v .vvp + +.v.vvp: + iverilog -Wanachronisms -Wimplicit -Wportbind -Wselect-range \ + -Winfloop -Wsensitivity-entire-vector \ + -Wsensitivity-entire-array \ + -o $@ $< + +bbu.vvp: bbu.v common.vh +test_stdlogic.vvp: test_stdlogic.v stdlogic.v common.vh +test_mac128pal.vvp: test_mac128pal.v test_stdlogic.v stdlogic.v \ + mac128pal.v common.vh + +clean: + rm -f $(TARGETS) diff --git a/hardware/fpga/bbu/README.md b/hardware/fpga/bbu/README.md index d367962..9ea55a9 100644 --- a/hardware/fpga/bbu/README.md +++ b/hardware/fpga/bbu/README.md @@ -1,5 +1,7 @@ # "BBU" Apple Custom Silicon +COMPILING: I use Icarus Verilog (`iverilog`) for simulation. + The "BBU" (Bob Bailey Unit), as it is called on the Macintosh SE's printed circuit board silkscreen, is a relatively complex Apple custom silicon chip, compared to the other custom chips on the Macintosh SE's diff --git a/hardware/fpga/bbu/bbu.v b/hardware/fpga/bbu/bbu.v index 63c8fe1..28caec3 100644 --- a/hardware/fpga/bbu/bbu.v +++ b/hardware/fpga/bbu/bbu.v @@ -1,22 +1,27 @@ /* Synthesizable Verilog hardware description for a drop-in replacement of the Apple Custom Silicon Bob Bailey Unit (BBU), an address controller for the Macintosh SE and similar computers. - + Written in 2020 by Andrew Makousky - + Public Domain Dedication: - + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. - + You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . */ +`ifndef BBU_V +`define BBU_V + +`include "common.vh" + // NOTE: These constants are defined in lower-case because they are // meant to be treated principally as if they are hard-wired // registers. @@ -172,7 +177,7 @@ module bbu_master_ctrl // MC68000 signals input wire a9, a17, a19, a20, a21, a22, a23; input wire r_n_w, n_as, n_uds, n_lds; - inout wire n_dtack; + `output_wz wire n_dtack; output wire n_ipl0; input wire n_ipl1; output wire n_berr; @@ -262,8 +267,8 @@ module bbu_master_ctrl ////////////////////////////////////////////////// // Pure combinatorial logic is defined first. - // TODO: Assert `*IPL0` if we receive an interrupt signal from the - // VIA or SCSI. However, do not assert `*IPL0` if the SCC asserts + // Assert `*IPL0` if we receive an interrupt signal from the VIA or + // SCSI. However, do not assert `*IPL0` if the SCC asserts // `*IPL1`. Guide to the Macintosh family hardware, page 113. // SCSI interrupts are signaled only on IRQ from the SCSI // controller. DRQ is not attached to MC68000 interrupt lines @@ -663,6 +668,12 @@ endmodule * 0xf00000 - 0xffffef: ??? (the ROM appears to be accessing here) * 0xfffff0 - 0xffffff: Auto Vector + TODO FIXME: Note that SCSI chip enable is NOT asserted when A9 is + one, and Macintosh Plus asserts DACK when A9 is one, but not when + it is zero. Okay, so I think I have that figured out. Add 512 for + DMA mode access logic, otherwise we do not implement DMA access + mode at all. + This address map has also been confirmed with Guide to the Macintosh family hardware, page 127. PLEASE NOTE: In SCC Read zone, if A0 == 1, then that is an SCC RESET. IWM must be A0 == 1, @@ -1099,7 +1110,7 @@ module dramctl_cpu (n_res, clk, r_n_w, c2m, // data output/input (RDQ) by placing those lanes in a // high-impedance state. output reg n_en245; - // *PMCYC principally enables the row/column address multiplexors. + // *PMCYC principally enables the row/column address multiplexers. // At a higher level, it is used to determine whether it is the // CPU's turn to access RAM or the BBU's turn to access RAM. The // CPU always takes a multiple of 4 clock cycles running at 8 MHz @@ -1112,7 +1123,7 @@ module dramctl_cpu (n_res, clk, r_n_w, c2m, input wire [9:0] ra; // RAM Address (RA) // In order to implement the memory overlay switch, we must snoop // the address bus. These are the registers we use to store the - // address multiplexor outputs. + // address multiplexer outputs. // N.B. RA7 and RA9 are set by us, but for simplicity of downstream // code, we capture them into the address snooping registers // regardless. @@ -1196,7 +1207,7 @@ module dramctl_cpu (n_res, clk, r_n_w, c2m, // triggers *PMCYC at the beginning of the CPU's turn to // access memory. // if (drc_state[1]) begin // State 2 - // // Enable the row address multiplexors. + // // Enable the row address multiplexers. // n_pmcyc <= 0; // // Trigger *EN245 as early as possible. // n_en245 <= 0; @@ -1217,7 +1228,7 @@ module dramctl_cpu (n_res, clk, r_n_w, c2m, drc_state_buf <= drc_state << 1; end if (drc_state[3]) begin // State 8 - // Enable the column address multiplexors. + // Enable the column address multiplexers. c2m <= 1; drc_state_buf <= drc_state << 1; end @@ -1267,7 +1278,7 @@ module dramctl_bbu (n_res, clk, r_n_w, output wire ram_r_n_w; // Row Address Strobe (*RAS), Column Address Strobe (*CAS) output reg n_ras, n_cas; - // *PMCYC principally enables the row/column address multiplexors. + // *PMCYC principally enables the row/column address multiplexers. // At a higher level, it is used to determine whether it is the // CPU's turn to access RAM or the BBU's turn to access RAM. The // CPU always takes a multiple of 4 clock cycles running at 8 MHz @@ -1350,7 +1361,7 @@ module dramctl_bbu (n_res, clk, r_n_w, // end if (drc_state[1]) begin // State 2 - // Enable the row address multiplexors. + // Enable the row address multiplexers. ra <= row_addr; drc_state_buf <= drc_state << 1; end @@ -1360,7 +1371,7 @@ module dramctl_bbu (n_res, clk, r_n_w, drc_state_buf <= drc_state << 1; end if (drc_state[3]) begin // State 8 - // Enable the column address multiplexors. + // Enable the column address multiplexers. ra <= col_addr; drc_state_buf <= drc_state << 1; end @@ -1506,6 +1517,12 @@ module avtimers (); // less ideal but easier to program would be to use two 16-bit // buffers as a FIFO. + // N.B. Sound generation. Since the original Macintosh used only + // simple counters and the registered ASG PAL for PWM generation, + // there is no way the more sophisticated PWM techniques could have + // been used. This is going to be a one-shot countdown timer for + // generating a single pulse per byte. + always @(negedge n_res) begin // Initialize all output registers on RESET. @@ -1525,5 +1542,64 @@ endmodule /* TODO: Summary of what is missing and left to implement: DRAM initialization pulses, DRAM refresh, detect 2.5MB of RAM and configure address buffers accordingly, video, disk, and audio - scanout, SCSI DMA, EXTDTK yielding, interrupt propagation, - double-check SCC read/write logic. */ + scanout, SCSI DMA, EXTDTK yielding. + + Okay, so the VERDICT on DRAM initialization pulses. We don't + actually use these as we should, strictly speaking, but why does it + still work? On power-on RESET, the first few CPU memory accesses + are all in ROM. Yet the BBU is still scanning the DRAM and + fetching words from it. These first few words will be garbage, but + it's okay because we're read-only. By the time the CPU makes its + first write to DRAM, all is well because it received a sufficient + number of *RAS initialization pulses. + + So really, the only mysteries left now is 2.5MB RAM detection and + 4MB RAM DRAM refresh. Then we need to do the busywork to implement + the PWM and video scanout modules and we're done! */ + +/* + +Now I think I see why there is the funny thing going on with the +address multiplexers for RAS/CAS. It is a required modification to +use DRAM fast-page mode since RAS and CAS are still logically +"swapped" compared to a contiguous memory layout. This swapping of +RAS and CAS is used to get DRAM refresh for free when scanning the +video framebuffer. + +Okay, so let's review in more detail. + +Address multiplexer row address outputs: + +A2, A3, A4, A5, A6, A7, A8, A10 + +A9 inputs directly to BBU, controls RA7. + +This is a straight match-up to DRAM row address lines. + +RA0 A2 +RA1 A3 +RA2 A4 +RA3 A5 +RA4 A6 +RA5 A7 +RA6 A8 +RA7 A9 +RA8 A10 +RA9 A19 (optional) (!) + +So, how many longwords for the video framebuffer? + +512 x 342 / 32 = 5472 longwords +In hex: 0x1560 +Number of address bits fully covered by a full scan: 12 + +Okay, so the question, does it work for DRAM refresh? Indeed it +does! Well, at least for <=1MB of RAM. + +RA9 looks to be trouble. But, the Unitron reverse engineering docs +almost have a solution. Set this to A17 (?) and it should "just work" +I guess. But why? + +*/ + +`endif // NOT BBU_V diff --git a/hardware/fpga/bbu/common.vh b/hardware/fpga/bbu/common.vh new file mode 100644 index 0000000..bafee2c --- /dev/null +++ b/hardware/fpga/bbu/common.vh @@ -0,0 +1,15 @@ +`ifndef COMMON_VH +`define COMMON_VH + +// Special type indicator for simclk: it will not be present in +// physical builds. +`define virtwire wire +// Simulate an output wire by using multi-cycle registered logic. +`define simwire reg +// Tristate output (output With high-impedance (Z)) +`define output_wz inout +// To preserve pin numbering at the I/O connections, power wires can +// be implemented as inputs: constant value 1 for Vcc, 0 for GND. +`define power input + +`endif // not COMMON_VH diff --git a/hardware/fpga/bbu/mac128pal.v b/hardware/fpga/bbu/mac128pal.v new file mode 100644 index 0000000..7ad95fa --- /dev/null +++ b/hardware/fpga/bbu/mac128pal.v @@ -0,0 +1,400 @@ +/* Implementation of Macintosh 128k/512k PALs in Verilog, + mainly to assist verification of the Macintosh SE BBU. They are + not identical, of course, but this should at least be helpful is + spotting significant flaws. + + Note that the signal names use here are the same as used in the + Unitron Macintosh clone's reverse engineering documentation. Many + of the signals are active low but not indicated here, for now. + + Written in 2020 by Andrew Makousky + + Public Domain Dedication: + + To the extent possible under law, the author(s) have dedicated all + copyright and related and neighboring rights to this software to + the public domain worldwide. This software is distributed without + any warranty. + + You should have received a copy of the CC0 Public Domain Dedication + along with this software. If not, see + . + +*/ + +/* Note: All arguments are listed in the order of the pinout of each + PAL chip. + + Another point, our cheap and easy way to hand self-referential PAL + logic equations is to simply treat them as registered sequential + logic equations and use enough simulation sub-cycles on them for + them to reach settle time. `simclk` controls these sub-cycles. + + Also note, PAL registers do not have a deterministic + initialization, hence the absence of a RESET signal. +*/ + +`ifndef MAC128PAL_V +`define MAC128PAL_V + +`include "common.vh" + +// PAL0-16R4: Timing State Machine +module tsm(simclk, clk, sysclk, pclk, s1, ramen, romen, as, uds, lds, gnd, + oe1, casl, cash, ras, vclk, q2, q1, s0, dtack, vcc); + input `virtwire simclk; + input wire clk; + input wire sysclk, pclk, s1, ramen, romen, as, uds, lds; + `power wire gnd; + input wire oe1; + output `simwire casl, cash; + output reg ras, vclk, q2, q1; + output `simwire s0, dtack; + `power wire vcc; + + // Simulate combinatorial logic sub-cycles. + always @(posedge simclk) begin + casl <= ~(~s0 & s1 & sysclk // video + | ~s0 & ~ramen & ~lds // processor + | ~s0 & ~casl & sysclk + | pclk & ~casl); + cash <= ~(~s0 & s1 & sysclk // video + | ~s0 & ~ramen & ~uds // processor + | ~s0 & ~cash & sysclk + | pclk & ~cash); + s0 <= ~(~ras & ~sysclk // 0 for `cas` and 1 for `ras` (estamos contando com o atraso da PAL) + | ~ras & ~s0); + dtack <= ~(~romen // se a ROM for de 250 nS ou SCC ou IWM + | ~ras & ~ramen & ~s1 // garante que vai ser reconhecido na descida de `pclk` no estado `s5` + | ~as & ~dtack & ramen // espera `as` subir para desativar + | ~as & ~dtack & ~s1); // mas evite ciclas de video (WE) + end + + // Simulate registered logic. + always @(posedge clk) begin + ras <= ~(~pclk & q1 & s1 // video cycle + | ~pclk & q1 & ~ramen & dtack // processor cycle + | pclk & ~ras); // any other (?) (segura mais um ciclo) cycle + vclk <= ~(~q1 & pclk & q2 & vclk // divide by 8 (1MHz) + | ~vclk & q1 + | ~vclk & ~pclk + | ~vclk & ~q2); + q1 <= ~(~pclk & q1 + | pclk & ~q1); // divide `pclk` by 2 (4MHz) + q2 <= ~(~q1 & pclk & q2 // divide by 4 (2MHz) + | ~q2 & q1 + | ~q2 & ~pclk); + end +endmodule + +// ras of processor: a16| a8| a7| a6| a5| a4| a3| a2| a1 +// cas of processor: a17|a16|a15|a14|a13|a12|a11|a10| a9 +// ras of video: pup| v8| v7| v6| v5| v4| v3| v2| v1 +// cas of video: pup|pup|vpg|3q1|3q4|v12|v11|v10| v9 +// ras of sound: pup|3q4|v12|v11|v10|?v8|?v9| v7| v6 +// cas of sound: pup|pup|pup|spg|pup|spg|spg|spg|3q1 + +// PAL1-16R8: Linear Address Generator +module lag(simclk, sysclk, p2io1, l28, va4, p0q2, vclk, va3, va2, va1, + gnd, oe2, vshft, vsync, hsync, s1, viapb6, snddma, + reslin, resnyb, vcc); + input `virtwire simclk; + input wire sysclk; + input wire p2io1, l28, va4, p0q2, vclk, va3, va2, va1; + `power wire gnd; + input wire oe2; + output reg vshft, vsync, hsync, s1, viapb6, snddma, reslin, resnyb; + `power wire vcc; + + // Simulate combinatorial logic sub-cycles. + always @(posedge simclk) begin + end + + // Simulate registered logic. + always @(posedge sysclk) begin + vshft <= ~(s1 & ~vclk & snddma); // um pulso depois da descida de `vclk` + vsync <= ~(reslin + | ~vsync & ~l28); + hsync <= ~(viapb6 & va4 & ~va3 & ~va2 & va1 // comec,a em29 (VA5) + | /*~ ???*/resnyb + | ~hsync & viapb6); // termina em 0F + s1 <= ~(~p0q2 // 0 for processor and 1 for video + | ~vclk + | ~vsync & hsync + | ~vsync & viapb6 // no vertical retrace s'o temos ciclos de som + | ~viapb6 & hsync & ~va4 & ~va3 & ~va2 + | ~viapb6 & ~hsync & (~va4 | va4 & ~va3 & ~va2 | + va4 & ~va3 & va2 & ~va1)); + viapb6 <= ~(~hsync & resnyb // 1 indicates horizontal retrace (pseudo VA6) + | va1 & ~viapb6 + | va2 & ~viapb6 + | ~hsync & ~viapb6 + | resnyb & ~viapb6 + | vshft & ~viapb6); + snddma <= ~(viapb6 & va4 & ~va3 & va2 & va1 & p0q2 & vclk & ~hsync // 0 nesta sa'ida + | ~snddma & vclk); // ... indicates sound cycle + reslin <= ~(0); // ??? tentamos gerar linha 370 + resnyb <= ~(vclk // incrementa VA5:VA14 em 0F e 2B + | viapb6 // ??? + | va1 + | va2 + | ~viapb6 & va3 + | hsync + | viapb6 & ~va3 + | ~hsync & va3 & ~va4 + | ~hsync & ~va3 & va4); + end +endmodule + +// 32 ciclas atiuas par linha - UA6..UA1 = 0 to 1F +// 1 ciclos de som/pwm = 2B +// 11 ciclos de retrac,o = 20 to 2A + +// 342 linhas ativas - VA6..VA14 = 010011100 to 111110001 +// 28 linhas de retrac,o = 010000000 to 010011011 + +// PAL2-16L8: Bus Management Unit 1 +module bmu1(simclk, va9, va8, va7, l15, va14, ovlay, a23, a22, a21, gnd, + as, csiwm, rd, cescc, vpa, romen, ramen, io1, l28, vcc); + input `virtwire simclk; + input wire va9, va8, va7, l15, va14, ovlay, a23, a22, a21; + `power wire gnd; + input wire as; + output `simwire csiwm, rd, cescc, vpa, romen, ramen, io1, l28; + `power wire vcc; + + // Simulate combinatorial logic sub-cycles. + always @(posedge simclk) begin + csiwm <= ~(a23 & a22 & ~a21 & ~as); // DFE1FF + rd <= ~(a23 & ~a22 & ~a21 & ~as); // 9FFFF8 + cescc <= ~(a23 & ~a22 & ~as); // 9FFFF8(R) or BFFFF9(W) + vpa <= ~(a23 & a22 & a21 & ~as); // acima de E00000 'e s'incrano + romen <= ~(~a23 & a22 & ~a21 & ~as // 400000 + | ~a23 & ~a22 & ~a21 & ~as & ovlay // (and 000000 with `ovlay`) + | a23 & ~a22 & ~as + | a23 & ~a21 & ~as); // para gerar DTACK (n~ao acessa ROM: A20) + ramen <= ~(~a23 & ~a22 & ~a21 & ~as & ~ovlay // 000000 + | ~a23 & a22 & a21 & ~as & ovlay); // (600000 with `ovlay`) + io1 <= ~(0); // ??? + l28 <= ~(~l15 & ~va9 & ~va8 & va7 // chegamos a 370 ou n~ao passamos da limha 28 + | ~l28 & ~va9 + | ~l28 & ~va8 + | ~l28 & ~va7); + end +endmodule + +// PAL3-16R4: Bus Management Unit 0 +module bmu0(simclk, sysclk, ramen, romen, va10, va11, va12, va13, va14, rw, + gnd, oe1, g244, we, ava14, l15, vid, ava13, servid, dtack, vcc); + input `virtwire simclk; + input wire sysclk; + input wire ramen, romen, va10, va11, va12, va13, va14, rw; + `power wire gnd; + input wire oe1; + output `simwire g244, we; + output reg ava14, l15, vid, ava13; + // N.B. Although this is nominally an output we can treat it as an + // input? + input wire servid, dtack; + `power wire vcc; + + // Simulate combinatorial logic sub-cycles. + always @(posedge simclk) begin + g244 <= ~(~ramen & rw + | ~g244 & ~ramen); + we <= ~(~ramen & ~rw + | ~we & ~dtack); // o dtack 'e mais curto antes de ciclo de video + end + + // Simulate registered logic. + always @(posedge sysclk) begin + ava14 <= ~(~va14 & ~va13); // + 1 + l15 <= ~(~va14 & ~va13 & ~va12 & ~va11 & ~va10 // n~ao passamos da linha 15 + | va14 & ~va13 & va12 & va11 & va10); // passamos de 368 + vid <= ~(servid); // aqui estamos invertendo: blanking est'a em `vshft` + ava13 <= ~(va13); // + 1 + end +endmodule + +// PAL4-16R6: Timing Signal Generator +module tsg(simclk, sysclk, vpa, a19, vclk, p0q1, e, keyclk, intscc, intvia, + gnd, oe3, d0, q6, clkscc, q4, q3, viacb1, pclk, ipl0, vcc); + input `virtwire simclk; + input wire sysclk; + input wire vpa, a19, vclk, p0q1, e, keyclk, intscc, intvia; + `power wire gnd; + input wire oe3; + output `simwire d0; + output reg q6, clkscc, q4, q3, viacb1, pclk; + output `simwire ipl0; + `power wire vcc; + + // Simulate combinatorial logic sub-cycles. + always @(posedge simclk) begin + ipl0 <= ~intscc | intvia; // CORRECTION + // ipl0 <= ~(0); // ??? /M nanda + d0 <= ~(~vpa & ~a19 & e); // F00000 amostra a fase como 0 /n e' + usado + end + + // Simulate registered logic. + always @(posedge sysclk) begin + // TODO VERIFY: q6 missing? + q6 <= ~(0); + clkscc <= ~(clkscc & ~pclk & ~q4 + | clkscc & ~pclk & ~q3 + | clkscc & ~pclk & vclk + | ~clkscc & pclk + | ~clkscc & q4 & q3 & ~vclk); // a cada 32 ciclos n~ao vira + viacb1 <= ~(0); // ??? /M nanda + pclk <= ~(pclk); // divide SYSCLK por 2 (8MHz) + q3 <= ~(~vclk); // `sysclk` / 16 + q4 <= ~(q4 & q3 & ~vclk // `sysclk` / 32 + | ~q4 & ~q3 // } J p/gerar CLKSCC + | ~q4 & vclk); + end +endmodule + +/* TODO: Now in order to fully implement the Macintosh's custom board + capabilities, we must as a baseline have an implementation of some + standard logic chips that are found on the Macintosh Main Logic + Board. This is where we implement the modules. */ +`include "stdlogic.v" + +// Wire that PAL cluster together, along with supporting standard +// logic chip. Here, we try to better indicate active high and active +// low because we also need to stick in a hex inverter chip. +module palcl(); + input `virtwire simclk; + `power wire vcc; + `power wire gnd; + input wire n_res; + input wire n_sysclk; // 16MHz + + // Clocks + // 8,4,3.686,2,1,1,0.5 MHz + output wire pclk, p0q1, clkscc, p0q2, vclk, q3, q4; + input wire e; // 6800 synchronous I/O "E" clock, ~1MHz + input wire keyclk; + + // TODO: Also implement dual video address counter ICs as part of + // the PAL cluster. + // Video address signals, comes from video counter IC + input wire va14, va13, va12, va11, va10, va9, va8, va7, + va4, va3, va2, va1; + // Audio address signals? + output wire ava14, ava13; + + // Video control signals + output wire n_vshft, n_vsync, n_hsync, n_snddma, vid; + input wire servid; + + // MC68000 CPU address signals + input wire a23, a22, a21, a20, a19, a17, a9; + input wire n_as, n_uds, n_lds; + output wire n_dtack; + input wire r_n_w; + inout wire d0, d1, d2, d3, d4, d5, d6, d7, + d8, d9, d10, d11, d12, d13, d14, d15; + + // Chip enable signals + output wire n_ramen, n_romen, n_csiwm, n_sccrd, n_cescc, n_vpa; + + // Interrupt signals + input wire n_intscc, n_intvia; + output wire n_ipl0; + + // VIA signals + output wire viapb6; // horizontal blanking + input wire ovlay; // Boot-time overlay + output wire viacb1; // keyboard interrupt + // wire d0; // Video timing phase sense signal + + // DRAM signals + output wire casl, cash, ras, we; + inout wire rdq0, rdq1, rdq2, rdq3, rdq4, rdq5, rdq6, rdq7, + rdq8, rdq9, rdq10, rdq11, rdq12, rdq13, rdq14, rdq15; + + // Address multiplexer signals? + output wire s0, s1, l28, l15, g244; + + // PAL chip-select and unknown "IO" signals + wire tsm_oe1, lag_oe2, bmu0_oe1, tsg_oe3; + + // Internal PAL cluster use only, supports video + wire reslin, resnyb; // video counter controllers? + wire p2io1, q6; + + // Wires to/from standard logic chips. + wire sysclk, snddma, n_a20, n_sndres, sndres, n_snd, snd, wr, n_wr; + wire n_245oe; + + // N.B.: *WR comes from IWM chip, WR goes to floppy drives. *A20 + // goes to VIA.CS1. *SNDDMA comes from the LAG. *SYSCLK comes + // from the 16MHz crystal oscillator. + + // *DMALD is generated by ASG. + + wire c8mf, c16mf, n_dmald, u12f_tc, ram_r_n_w_f; + wire vmsh; // video mid-shift, connect two register chips together + wire n_lermd; // ??? + wire n_ldps; + wire s5; + wire ra8, ra9; + + wire vid_n_a4; // ??? + + // TODO FIXME! We're not using assign correctly! `assign` implies + // diode isolation between separate nets. We want to merge + // multiple names together for the same net. + + // N.B. on PCB, use c8mf for high-frequency signal + // filter/conditioning. + assign c8mf = pclk; + assign c16mf = sysclk; + assign ram_r_n_w_f = we; + + /* N.B. The reason why phase calibration is required in the + Macintosh is because the PALs do not have a RESET pin. It is + the logic designer's discretion to implement one explicitly, of + they could forgo it to allow for more I/O pins. Hence the + motivation to use software phase correction instead. */ + + f04 u4d(n_sysclk, sysclk, n_snddma, snddma, a20, n_a20, gnd, + n_sndres, sndres, n_snd, snd, wr, n_wr, vcc); + + ls161 u13e(n_sndres, c8mf, rdq12, rdq13, rdq14, rdq15, n_snd, gnd, + n_dmald, u12f_tc, , , , , snd, vcc); + ls161 u12f(n_sndres, c8mf, rdq8, rdq9, rdq10, rdq11, n_snd, gnd, + n_dmald, n_sndres, , , , , u12f_tc, vcc); + ls166 u10f(vmsh, rdq8, rdq9, rdq10, rdq11, 1'b0, c16mf, gnd, + s5, rdq12, rdq13, rdq14, n_lermd, rdq15, n_ldps, vcc); + ls166 u11f(s5, rdq0, rdq1, rdq2, rdq3, 1'b0, c16mf, gnd, + s5, rdq4, rdq5, rdq6, vmsh, rdq7, n_ldps, vcc); + ls245 u9e(ram_r_n_w_f, rdq0, rdq1, rdq2, rdq3, rdq4, rdq5, rdq6, rdq7, + gnd, d7, d6, d5, d4, d3, d2, d1, d0, n_245oe, vcc); + ls245 u10e(ram_r_n_w_f, rdq8, rdq9, rdq10, rdq11, rdq12, rdq13, rdq14, + rdq15, gnd, d15, d14, d13, d12, d11, d10, d9, d8, + n_245oe, vcc); + f253 u10g(snddma, vid_n_a4, va7, s5, a9, a17, ra8, gnd, + ra9, a20, a19, s5, s5, p0q2/*c2m*/, 1'b0, vcc); + + // asg u11e(c16mf, rdq0, rdq1, rdq2, rdq3, rdq4, rdq5, n_dma, vclk, gnd, + // tsen2, n_dmald, pwm, , , , , , , vcc); + + tsm pal0(simclk, sysclk, sysclk, pclk, s1, n_ramen, n_romen, n_as, n_uds, n_lds, + gnd, tsm_oe1, casl, cash, ras, vclk, p0q2, p0q1, s0, n_dtack, vcc); + lag pal1(simclk, sysclk, p2io1, l28, va4, p0q2, vclk, va3, va2, va1, + gnd, lag_oe2, n_vshft, n_vsync, n_hsync, s1, viapb6, + n_snddma, reslin, + resnyb, vcc); + bmu1 pal2(simclk, va9, va8, va7, l15, va14, ovlay, a23, a22, a21, gnd, + n_as, n_csiwm, n_sccrd, n_cescc, n_vpa, n_romen, n_ramen, p2io1, l28, vcc); + bmu0 pal3(simclk, sysclk, n_ramen, n_romen, va10, va11, va12, va13, va14, + r_n_w, gnd, bmu0_oe1, g244, we, ava14, l15, vid, ava13, + servid, n_dtack, vcc); + tsg pal4(simclk, sysclk, n_vpa, a19, vclk, p0q1, e, keyclk, n_intscc, + n_intvia, gnd, tsg_oe3, d0, q6, clkscc, q4, q3, viacb1, + pclk, n_ipl0, vcc); +endmodule + +`endif // not MAC128PAL_V diff --git a/hardware/fpga/bbu/stdlogic.v b/hardware/fpga/bbu/stdlogic.v new file mode 100644 index 0000000..ed66bb4 --- /dev/null +++ b/hardware/fpga/bbu/stdlogic.v @@ -0,0 +1,426 @@ +/* Implementation of standard logic integrated circuits in Verilog to + facilitate board-level simulations of glue logic. DIP pinout. + + Written in 2020 by Andrew Makousky + + Public Domain Dedication: + + To the extent possible under law, the author(s) have dedicated all + copyright and related and neighboring rights to this software to + the public domain worldwide. This software is distributed without + any warranty. + + You should have received a copy of the CC0 Public Domain Dedication + along with this software. If not, see + . + +*/ + +`ifndef STDLOGIC_V +`define STDLOGIC_V + +`include "common.vh" + +// F00: Quad NAND gates. +module f00(s1a, s1b, s1y, s2a, s2b, s2y, gnd, + s3y, s3b, s3a, s4y, s4b, s4a, vcc); + input wire s1a, s1b; + output wire s1y; + input wire s2a, s2b; + output wire s2y; + `power wire gnd; + output wire s3y; + input wire s3b, s3a; + output wire s4y; + input wire s4b, s4a; + `power wire vcc; + + assign s1y = ~(s1a & s1b); + assign s2y = ~(s2a & s2b); + assign s3y = ~(s3a & s3b); + assign s4y = ~(s4a & s4b); +endmodule + +// F02: Quad NOR gates. +module f02(s1y, s1a, s1b, s2y, s2a, s2b, gnd, + s3a, s3b, s3y, s4a, s4b, s4y, vcc); + output wire s1y; + input wire s1a, s1b; + output wire s2y; + input wire s2a, s2b; + `power wire gnd; + input wire s3a, s3b; + output wire s3y; + input wire s4a, s4b; + output wire s4y; + `power wire vcc; + + assign s1y = ~(s1a | s1b); + assign s2y = ~(s2a | s2b); + assign s3y = ~(s3a | s3b); + assign s4y = ~(s4a | s4b); +endmodule + +// F04: Hex inverters. +module f04(a0, q0, a1, q1, a2, q2, gnd, q5, a5, q4, a4, q3, a3, vcc); + input wire a0; + output wire q0; + input wire a1; + output wire q1; + input wire a2; + output wire q2; + `power wire gnd; + output wire q5; + input wire a5; + output wire q4; + input wire a4; + output wire q3; + input wire a3; + `power wire vcc; + + assign q0 = ~a0; + assign q1 = ~a1; + assign q2 = ~a2; + assign q3 = ~a3; + assign q4 = ~a4; + assign q5 = ~a5; +endmodule + +// F08: Quad AND gates. +module f08(s1a, s1b, s1y, s2a, s2b, s2y, gnd, + s3y, s3a, s3b, s4y, s4a, s4b, vcc); + input wire s1a, s1b; + output wire s1y; + input wire s2a, s2b; + output wire s2y; + `power wire gnd; + output wire s3y; + input wire s3a, s3b; + output wire s4y; + input wire s4a, s4b; + `power wire vcc; + + assign s1y = s1a & s1b; + assign s2y = s2a & s2b; + assign s3y = s3a & s3b; + assign s4y = s4a & s4b; +endmodule + +// F32: Quad OR gates. +module f32(s1a, s1b, s1y, s2a, s2b, s2y, gnd, + s3y, s3a, s3b, s4y, s4a, s4b, vcc); + input wire s1a, s1b; + output wire s1y; + input wire s2a, s2b; + output wire s2y; + `power wire gnd; + output wire s3y; + input wire s3a, s3b; + output wire s4y; + input wire s4a, s4b; + `power wire vcc; + + assign s1y = s1a | s1b; + assign s2y = s2a | s2b; + assign s3y = s3a | s3b; + assign s4y = s4a | s4b; +endmodule + +// LS161: 4-bit binary counter. +// Used to generate sequential video and sound RAM addresses. +module ls161(n_clr, clk, a, b, c, d, enp, gnd, + n_load, ent, q_d, q_c, q_b, q_a, rco, vcc); + input wire n_clr, clk, a, b, c, d, enp; + `power wire gnd; + input wire n_load, ent; + output wire q_d, q_c, q_b, q_a; + output wire rco; + `power wire vcc; + + wire [3:0] loadvec = { d, c, b, a }; + reg [3:0] outvec; + + assign rco = (outvec == 4'hf); + assign { q_d, q_c, q_b, q_a } = outvec; + + // N.B. As soon as the rising edge of the clock is detected under + // the proper conditions, we propagate the incremented value to the + // output pins. We do not wait until the next rising edge of the + // clock. + always @(posedge clk) begin + if (~n_clr) outvec <= 0; + else if (~n_load) outvec <= loadvec; + else if (enp & ent) + outvec <= outvec + 1; + // else Nothing to be done. + end +endmodule + +// LS165: 8-bit Parallel In, Serial Out shift register. Very similar +// to LS166 but *Q_H instead of *CLR and different pinout. +module ls165(sh_n_ld, clk, e, f, g, h, n_q_h, gnd, + q_h, ser, a, b, c, d, clk_inh, vcc); + input wire sh_n_ld, clk, e, f, g, h; + output wire n_q_h; + `power wire gnd; + output wire q_h; + input wire ser, a, b, c, d, clk_inh; + `power wire vcc; + + wire n_int_clk = ~(clk_inh | clk); + reg [7:0] int_reg; + + assign q_h = int_reg[7]; + assign n_q_h = ~q_h; + + // N.B. As soon as the falling edge of the clock is detected under + // the proper conditions, we propagate the shifted value to the + // output pins. We do not wait until the next falling edge of the + // clock. + always @(negedge n_int_clk) begin + if (sh_n_ld) + int_reg <= { int_reg[6:0], ser }; + else + int_reg <= { h, g, f, e, d, c, b, a }; + end +endmodule + +// LS166: 8-bit Parallel In, Serial Out shift register. +// Used to generate the TTL serial video signal. +module ls166(ser, a, b, c, d, clk_inh, clk, gnd, + n_clr, e, f, g, q_h, h, sh_n_ld, vcc); + input wire ser, a, b, c, d, clk_inh, clk; + `power wire gnd; + input wire n_clr, e, f, g; + output wire q_h; + input wire h, sh_n_ld; + `power wire vcc; + + wire n_int_clk = ~(clk_inh | clk); + reg [7:0] int_reg; + + assign q_h = int_reg[7]; + + always @(negedge n_clr) + int_reg <= 0; + + // N.B. As soon as the falling edge of the clock is detected under + // the proper conditions, we propagate the shifted value to the + // output pins. We do not wait until the next falling edge of the + // clock. + always @(negedge n_int_clk) begin + if (n_clr) begin + if (sh_n_ld) + int_reg <= { int_reg[6:0], ser }; + else + int_reg <= { h, g, f, e, d, c, b, a }; + end + end +endmodule + +// LS245: Octal bus transceivers. +// Used to control DRAM access from the CPU data bus. +module ls245(dir, a1, a2, a3, a4, a5, a6, a7, a8, gnd, + b8, b7, b6, b5, b4, b3, b2, b1, n_oe, vcc); + input wire dir; + inout wire a1, a2, a3, a4, a5, a6, a7, a8; + `power wire gnd; + inout wire b8, b7, b6, b5, b4, b3, b2, b1; + input wire n_oe; + `power wire vcc; + + // (~dir) => (ax <= bx), (dir) => (bx <= ax) + + // N.B. `assign` implements diode isolation to enforce a + // directional output drive, so we can't assemble bi-directional + // bit vectors for more compact code in that fashion. `alias` from + // System Verilog would make that possible, though. Though we + // could define a sub-module to use bit vectors, it turns out that + // consumes just as many lines of code. So we just go repetitive. + + assign a1 = (n_oe | dir) ? 8'bz : b1; + assign a2 = (n_oe | dir) ? 8'bz : b2; + assign a3 = (n_oe | dir) ? 8'bz : b3; + assign a4 = (n_oe | dir) ? 8'bz : b4; + assign a5 = (n_oe | dir) ? 8'bz : b5; + assign a6 = (n_oe | dir) ? 8'bz : b6; + assign a7 = (n_oe | dir) ? 8'bz : b7; + assign a8 = (n_oe | dir) ? 8'bz : b8; + + assign b1 = (n_oe | ~dir) ? 8'bz : a1; + assign b2 = (n_oe | ~dir) ? 8'bz : a2; + assign b3 = (n_oe | ~dir) ? 8'bz : a3; + assign b4 = (n_oe | ~dir) ? 8'bz : a4; + assign b5 = (n_oe | ~dir) ? 8'bz : a5; + assign b6 = (n_oe | ~dir) ? 8'bz : a6; + assign b7 = (n_oe | ~dir) ? 8'bz : a7; + assign b8 = (n_oe | ~dir) ? 8'bz : a8; +endmodule + +// F138: 3-to-8 line decoder/demultiplexer, active low. +module f138(a0, a1, a2, n_e1, n_e2, e3, n_y7, gnd, + n_y6, n_y5, n_y4, n_y3, n_y2, n_y1, n_y0, vcc); + input wire a0, a1, a2, n_e1, n_e2, e3; + output wire n_y7; + `power wire gnd; + output wire n_y6, n_y5, n_y4, n_y3, n_y2, n_y1, n_y0; + `power wire vcc; + + wire [2:0] va; + wire en; + + assign va = { a2, a1, a0 }; + assign en = ~n_e1 & ~n_e2 & e3; + + assign n_y0 = ~(en & (va == 0)); + assign n_y1 = ~(en & (va == 1)); + assign n_y2 = ~(en & (va == 2)); + assign n_y3 = ~(en & (va == 3)); + assign n_y4 = ~(en & (va == 4)); + assign n_y5 = ~(en & (va == 5)); + assign n_y6 = ~(en & (va == 6)); + assign n_y7 = ~(en & (va == 7)); +endmodule + +// F238: 3-to-8 line decoder/demultiplexer, active high. +module f238(a0, a1, a2, n_e1, n_e2, e3, y7, gnd, + y6, y5, y4, y3, y2, y1, y0, vcc); + input wire a0, a1, a2, n_e1, n_e2, e3; + output wire y7; + `power wire gnd; + output wire y6, y5, y4, y3, y2, y1, y0; + `power wire vcc; + + wire [2:0] va; + wire en; + + assign va = { a2, a1, a0 }; + assign en = ~n_e1 & ~n_e2 & e3; + + assign y0 = en & (va == 0); + assign y1 = en & (va == 1); + assign y2 = en & (va == 2); + assign y3 = en & (va == 3); + assign y4 = en & (va == 4); + assign y5 = en & (va == 5); + assign y6 = en & (va == 6); + assign y7 = en & (va == 7); +endmodule + +// F253: Dual 4-to-1 multiplexer. +// Used for Macintosh Plus CPU/video/sound address selection. +module f253(n_1g, b, s1c3, s1c2, s1c1, s1c0, s1y, gnd, + s2y, s2c0, s2c1, s2c2, s2c3, a, n_2g, vcc); + input wire n_1g, b, s1c3, s1c2, s1c1, s1c0; + `output_wz wire s1y; + `power wire gnd; + `output_wz wire s2y; + input wire s2c0, s2c1, s2c2, s2c3, a, n_2g; + `power wire vcc; + + assign s1y = (n_1g) ? 'bz : sel1({ b, a }); + function sel1(input [1:0] selvec); + case (selvec) + 0: sel1 = s1c0; + 1: sel1 = s1c1; + 2: sel1 = s1c2; + 3: sel1 = s1c3; + endcase + endfunction + + assign s2y = (n_2g) ? 'bz : sel2({ b, a }); + function sel2(input [1:0] selvec); + case (selvec) + 0: sel2 = s2c0; + 1: sel2 = s2c1; + 2: sel2 = s2c2; + 3: sel2 = s2c3; + endcase + endfunction +endmodule + +// F257: Quadruple 2-to-1 multiplexer. Used for RAS/CAS selection. +// LS257: Logically the same as F257. (Only differs electrically.) +module f257(n_a_b, s1a, s1b, s1y, s2a, s2b, s2y, gnd, + s3y, s3b, s3a, s4y, s4b, s4a, n_oe, vcc); + input wire n_a_b, s1a, s1b; + `output_wz wire s1y; + input wire s2a, s2b; + `output_wz wire s2y; + `power wire gnd; + `output_wz wire s3y; + input wire s3b, s3a; + `output_wz wire s4y; + input wire s4b, s4a, n_oe; + `power wire vcc; + + assign s1y = (n_oe) ? 'bz : (~n_a_b) ? s1a : s1b; + assign s2y = (n_oe) ? 'bz : (~n_a_b) ? s2a : s2b; + assign s3y = (n_oe) ? 'bz : (~n_a_b) ? s3a : s3b; + assign s4y = (n_oe) ? 'bz : (~n_a_b) ? s4a : s4b; +endmodule + +// LS393: Dual 4-bit binary counter. +// Used to generate sequential video and sound RAM addresses. +module ls393(s1a, s1clr, s1q_a, s1q_b, s1q_c, s1q_d, gnd, + s2q_d, s2q_c, s2q_b, s2q_a, s2clr, s2a, vcc); + input wire s1a, s1clr; + output wire s1q_a, s1q_b, s1q_c, s1q_d; + `power wire gnd; + output wire s2q_d, s2q_c, s2q_b, s2q_a; + input wire s2clr, s2a; + `power wire vcc; + + reg [3:0] s1reg; + reg [3:0] s2reg; + + assign { s1q_d, s1q_c, s1q_b, s1q_a } = s1reg; + assign { s2q_d, s2q_c, s2q_b, s2q_a } = s2reg; + + always @(posedge s1clr) + s1reg <= 0; + always @(posedge s1a) + s1reg <= s1reg + 1; + always @(posedge s2clr) + s2reg <= 0; + always @(posedge s2a) + s2reg <= s2reg + 1; + +endmodule + +// LS595: 8-bit serial input, parallel output shift register, with +// output latch. +module ls595(q_b, q_c, q_d, q_e, q_f, q_g, q_h, gnd, + n_q_h, n_srclr, srclk, rclk, n_oe, ser, vcc); + output wire q_b, q_c, q_d, q_e, q_f, q_g, q_h; + `power wire gnd; + output wire n_q_h; + input wire n_srclr, srclk, rclk, n_oe, ser; + output wire q_a; + `power wire vcc; + + reg [7:0] int_reg; + reg [7:0] out_reg; + + assign { q_h, q_g, q_f, q_e, q_d, q_c, q_b, q_a } + = (n_oe) ? 8'bz : int_reg; + assign n_q_h = q_h; + + always @(negedge n_srclr) begin + int_reg <= 0; + end + + always @(posedge srclk) begin + int_reg = { int_reg[7:1], ser }; + end + + // N.B. As soon as the rising edge of the clock is detected under + // the proper conditions, we propagate the shifted value to the + // output pins. We do not wait until the next rising edge of the + // clock. + always @(posedge rclk) begin + out_reg <= int_reg; + end +endmodule + +`endif // not STDLOGIC_V diff --git a/hardware/fpga/bbu/test_mac128pal.v b/hardware/fpga/bbu/test_mac128pal.v new file mode 100644 index 0000000..14401b1 --- /dev/null +++ b/hardware/fpga/bbu/test_mac128pal.v @@ -0,0 +1,31 @@ +`timescale 1ns/100ps + +`include "mac128pal.v" + +`include "test_stdlogic.v" + +module test_mac128pal(); + // Instantiate individual test modules. + test_ls161 tu0(); + test_ls245 tu1(); + + // Perform the remainder of global configuration here. + + // Set simulation time limit. + initial begin + #480 $finish; + end + + // We can use `$display()` for printf-style messages and implement + // our own automated test suite that way if we wish. + initial begin + $display("Example message: Start of simulation. ", + "(time == %1.0t)", $time); + end + + // Log to a VCD (Variable Change Dump) file. + initial begin + $dumpfile("test_mac128pal.vcd"); + $dumpvars; + end +endmodule diff --git a/hardware/fpga/bbu/test_stdlogic.v b/hardware/fpga/bbu/test_stdlogic.v new file mode 100644 index 0000000..1e40cda --- /dev/null +++ b/hardware/fpga/bbu/test_stdlogic.v @@ -0,0 +1,107 @@ +`ifndef TEST_STDLOGIC_V +`define TEST_STDLOGIC_V + +`include "stdlogic.v" + +module test_ls161(); + wire vcc, gnd; + reg n_res; + reg clock; + reg simclk; + + reg n_clr, n_load, enp, ent; + reg [3:0] loadvec; + wire [3:0] outvec; + wire rco; + + assign vcc = 1; + assign gnd = 0; + + ls161 u0_ls161(n_clr, clock, + loadvec[0], loadvec[1], loadvec[2], loadvec[3], + enp, gnd, + n_load, ent, + outvec[3], outvec[2], outvec[1], outvec[0], + rco, vcc); + + // Trigger RESET at beginning of simulation. Make sure there is an + // initial falling edge. + initial begin + n_res = 1; + #2 n_res = 0; + #18 n_res = 1; + end + + // Initialize clock. + initial begin + clock = 0; + simclk = 0; + end + + // 10 unit clock cycle. + always + #5 clock = ~clock; + + // Sub-cycle simulator clock triggers as fast as possible. + always + #1 simclk = ~simclk; + + // Play with input values a bit. + initial begin + n_load = 1; + n_clr = 1; + loadvec = 4'hc; + enp = 1; + ent = 1; + #2 n_clr = 0; + #18 n_clr = 1; + #222 n_load = 0; + #18 n_load = 1; + #40 enp = 0; + #20 enp = 1; + #40 ent = 0; + #20 ent = 1; + end +endmodule + +module test_ls245(); + wire vcc, gnd; + + reg dir, n_oe, n_oe_a, n_oe_b; + reg [7:0] drv_a, drv_b; + wire [7:0] sa, sb; // sense_a, sense_b + + assign vcc = 1; + assign gnd = 0; + + assign sa = (n_oe_a) ? 8'bz : drv_a; + assign sb = (n_oe_b) ? 8'bz : drv_b; + + ls245 u0_ls245(dir, sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], + sa[6], sa[7], gnd, sb[7], sb[6], sb[5], sb[4], + sb[3], sb[2], sb[1], sb[0], n_oe, vcc); + + // Play with input values a bit. + initial begin + dir = 0; + n_oe = 1; + n_oe_a = 1; + n_oe_b = 1; + drv_a = 0; + drv_b = 0; + #10 n_oe = 0; + #10 n_oe_b = 0; + #10 drv_b = 8'hc6; + #10 drv_b = 8'h35; + #10 n_oe_b = 1; n_oe_a = 0; + #10 dir = 1; + #10 drv_a = 8'h7f; + #10 n_oe_a = 1; n_oe_b = 0; dir = 0; + #10 n_oe_a = 0; n_oe_b = 1; dir = 1; + #10 n_oe_b = 0; // Test a conflict condition. + #10 dir = 0; n_oe_a = 1; // Release the conflict. + #10 n_oe = 1; n_oe_a = 1; n_oe_b = 1; + end +endmodule + +`endif // not TEST_STDLOGIC_V