A2PDP11 (15) Apple II インターフェースの設計
Apple II との接続方法を検討します。
Apple II のバスインターフェースとして、スロット I/O ($C0nx) アドレスを使うことにします。
$C0n0 : 入力ハンドシェーク
rstb:Apple II からの読み出し時、ビット 7 が 1 なら、入力可能を示す。
rrdy:Apple II のキー入力時、ビット 7 に 1 を書き込む。
$C0n1 : 入力データ
rbuf:Apple II キー入力コード(ASCII 8 ビット)。
$C0n2 : 表示ハンドシェーク
xstb:Apple II からの読み出し時、ビット 7 が 1 なら、表示待ちデータがあることを示す。
xrdy:Apple II が表示データを受付可能な時、ビット 7 に 1 を書き込む。
$C0n3 : 表示データ
xbud:ODT からの表示データ(ASCII 8 ビット)。
Tang Nano 9K の入出力ポートが足りないので、Apple II のアドレス信号と DAL を多重化します。修正した回路図を下記に示します。
/DALLO_OE で、U2-U3 と U4-U5 の出力を切り替えます。
tov.sv を修正します。
`default_nettype none
// TEST17 2024.11.17 Apple II Interface
module top (
inout wire [15:0] dal, // DAL<21:0>, BS<1:0>
input wire [3:0] aio, // AIO<3:0>
inout wire [7:0] d, // Apple II Data D<7:0>
input wire devsel_n, // Apple II /DEVSEL
input wire rw, // Apple II R/W
input wire bufctl_n,
input wire ale_n,
output wire nxm_n,
input wire sctl_n,
input wire clk,
output reg cont_n,
output reg miss_n,
output reg dallo_oe_n,
output reg event_n,
output reg irq0,
output reg irq1,
output wire [1:0] O_psram_ck,
output wire [1:0] O_psram_ck_n,
inout wire [15:0] IO_psram_dq,
inout wire [1:0] IO_psram_rwds,
output wire [1:0] O_psram_cs_n,
output wire [1:0] O_psram_reset_n,
input wire mclk,
input wire rst_n,
output wire led1
);
// AIO CODE
parameter NIO = 4'b1111; // internal operation only, no I/O
parameter GP_READ = 4'b1110; // General-Purpose read
parameter INTERRUPT_ACK = 4'b1101; // Interrupt acknowledge, vector read
parameter REQUEST_READ = 4'b1100; // Instruction-stream request read
parameter RMW_NOLOCK = 4'b1011; // Read/Modify/Write - no bus lock
parameter RMW_BUSLOCK = 4'b1010; // Read/Modify/Write - bus lock
parameter DATA_READ = 4'b1001; // Data-stream read
parameter DEMAND_READ = 4'b1000; // Instruction-stream demand read
parameter GP_WRITE = 4'b0101; // General-Purpose word write
parameter BYTE_WRITE = 4'b0011; // Bus byte write
parameter WORD_WRITE = 4'b0001; // Bus word write
// BANK SELECT
parameter BS_MEM = 2'b00; // Memory
parameter BS_SYS = 2'b01; // System register
parameter BS_EXT = 2'b10; // Extarnal I/O
parameter BS_INT = 2'b11; // Internal register
// GP CODE
parameter POWER_UP0 = 8'o000; // Reads the power-up mode
parameter POWER_UP2 = 8'o002; // Reads the power-up mode, clears the FPA’s FPS
// DLART
parameter DLART = 19'o1777756; // DLART registers
parameter RCSR = 22'o17777560; // Receiver Control and Status Register
parameter RBUF = 22'o17777562; // Receiver Buffer Register
parameter XCSR = 22'o17777564; // Transmitter Control And Status Register
parameter XBUF = 22'o17777566; // Transmitter Buffer Register
// PC11 Paper Tape Reader/Punch
parameter PC11 = 19'o1777755; // PC11 registers
parameter PRS = 22'o17777550; // Paper Tape Reader Status Register
parameter PRB = 22'o17777552; // Paper Tape Reader Buffer Register
parameter PPS = 22'o17777554; // Paper Tape Punch Status Register
parameter PPB = 22'o17777556; // Paper Tape Punch Buffer Register
parameter HIMEM = 22'o17757777; // End of RAM
// Apple II Register
parameter A2RCSR = 8'h00; // Console out status; Read = rstb, Write = rrdy
parameter A2RBUF = 8'h01; // Cousole out data
parameter A2XCSR = 8'h02; // Console in status; Read = xstb, Write = xrdy
parameter A2XBUF = 8'h03; // Console in data
Apple II アドレスの取り込み
clk の 3 倍周期の clk_x3 を使い、dal に多重化された信号を取り出します。ALE がアサートされるタイミングで dallo_oe_n をアサートし、Apple II アドレス a[7:0] を取り込みます。
logic clk_x3; // clk x 3 = 54 MHz
Gowin_rPLL rpll(
.clkout(clk_x3), //output clkout
.clkin(clk) //input clkin
);
assign event_n = 1'b1;
assign irq0 = 1'b0;
assign irq1 = 1'b0;
logic [3:0] count;
logic [15:0] mdallo;
logic [21:0] mdal;
logic [3:0] maio;
logic [1:0] mbs;
logic [7:0] gp_code;
logic [7:0] a; // Apple II Address
always_ff@(posedge clk_x3) begin
if (ale_n) begin
count <= 0;
end else begin
if (count == 0) begin
dallo_oe_n <= 1'b1;
mdallo <= dal;
end else if (count == 1) begin
if ((aio == GP_READ) || (aio == GP_WRITE)) begin
gp_code <= mdallo[7:0];
end else begin
gp_code <= 8'b11111111;
end
maio <= aio;
mbs[0] <= dal[6];
mbs[1] <= dal[7];
mdal[21] <= dal[8];
mdal[20] <= dal[0];
mdal[19] <= dal[9];
mdal[18] <= dal[10];
mdal[17] <= dal[11];
mdal[16] <= dal[12];
mdal[15:0] <= mdallo;
end else if (count == 2) begin
a[0] <= dal[5];
a[1] <= dal[4];
a[2] <= dal[3];
a[3] <= dal[2];
a[4] <= dal[1];
a[5] <= dal[15];
a[6] <= dal[14];
a[7] <= dal[13];
dallo_oe_n <= 1'b0;
end
count <= count + 1'b1;
end
end
ABORT (nxm_n) の生成
メモリ実装範囲外、DLART と PC11(ペーパーテープ)以外の外部 I/O は、エラーとします。
assign nxm_n = sctl_n ? 1'b1 :
((maio[2] == 0) && (mbs == BS_MEM) && (mdal > HIMEM)) ? 1'b0 :
((maio[2] == 0) && (mbs == BS_EXT) &&
((mdal[21:3] != DLART) && (mdal[21:3] != PC11))) ? 1'b0 :
1'b1;
Console ODT の制御部
Apple II の /DEVSEL 信号を CLK に同期させます。
Apple II のスロット I/O アドレスにアクセスすると、/DEVSEL がローになります。
I/O アドレスの読み出し時に以下を DAL に出力します。
rstb は、コンソール ODT が入力を受け付けることを示します。
xstb は、コンソール ODT からの表示データがあることを示します。
xbuf は、コンソール ODT からの表示データです。
I/O アドレスの書き込み時に以下を DAL から取り込みます。
rrdy は、キー入力データがあることを示します。
rbuf は、コンソール ODT への入力データです。
xrdy は、コンソール ODT からの出力を Apple II が受け付けることを示します。
logic [7:0] xbuf;
logic [7:0] rbuf;
logic xrdy;
logic rrdy;
logic [7:0] d0;
assign d = rw ? d0 : 8'bz;
logic devsel0;
logic devsel1;
always_ff@(posedge clk) begin
devsel0 <= !devsel_n;
devsel1 <= devsel0;
end
logic dev_done;
always_ff@(posedge clk) begin
if (devsel1) begin
if (dev_done) begin;
if (rw) begin
case (a[3:0])
A2RCSR : d0 <= {rstb, 7'b0};
A2XCSR : d0 <= {xstb, 7'b0};
A2XBUF : d0 <= xbuf;
endcase
end else begin
case (a[3:0])
A2RCSR : rrdy <= d[7];
A2RBUF : rbuf <= d;
A2XCSR : xrdy <= d[7];
endcase
end
dev_done <= 1'b0;
end
end else begin
dev_done <= 1'b1;
end
end
コンソール ODT の表示処理
アドレス XBUF の読み出し時に、xbuf を設定し、xstb を 1 にし、表示データがあることを知らせます。
xstb が 1 の状態で、xrdy が 0 になるのを待ちます。Apple II 側では表示が完了すると xrdy を 0 にします。xrdy が 0 になると、xstb を 0 にします。
xstb が 0 の状態で、xrdy が 1 になるのを待ちます。Apple II 側では、xstb が 0 の時、xrdy を 1 にします。
xdone は、Transmitter Control And Status Register (XCSR) の done フラグで、コンソール ODT から出力があることを示します。
logic xdone;
logic xstb;
always_ff@(negedge clk) begin
if (gp_code == 8'o014) begin
xdone <= 1'b1;
xstb <= 1'b0;
end else if ((sctl_n == 0) && (mdal == XBUF)) begin
xstb <= 1'b1;
xdone <= 1'b0;
xbuf <= dal[7:0];
end else if (xstb & !xrdy) begin
xstb <= 1'b0;
end else if (!xstb & xrdy) begin
xdone <= 1'b1;
end
end
コンソール ODT の入力処理
Apple II のキー入力がある時、rrdy を 1 にします。
Apple II は、rstb が 1 の時、キー入力データをバスに出力します。
rrdy が 1 の時、rstb を 1 にし、入力を受け付けたことを示します。
rstb が 1 の時、rstb をクリアし、rdone をセットします。
RBUF の読み出し時に、キー入力データを dal に出力します。
rdone は、Receiver Control/Status Register (RCSR) の done フラグで、コンソールへの入力があることを示します。
logic rdone;
logic rstb;
always_ff@(negedge clk) begin
if (gp_code == 8'o014) begin
rdone <= 1'b0;
rstb <= 1'b0;
end else if (rrdy) begin
rstb <= 1'b1;
end else if (rstb) begin
rstb <= 1'b0;
rdone <= 1'b1;
end else if (mdal == RBUF) begin
rdone <= 1'b0;
end
end
DAL の出力
コンソール ODT、ペーパーテープ(なし)、パワーアップ構成レジスタ、RAM 読み出しの処理を行います。
assign dal = bufctl_n ? 16'bz :
(mdal == RCSR) ? {8'b0, rdone, 7'b0} :
(mdal == XCSR) ? {8'b0, xdone, 7'b0} :
(mdal == RBUF) ? {8'b0, rbuf} :
(mdal == PRS) ? 16'b1000_0000_0000_0000 :
(mdal == PRB) ? 16'b0 :
(mdal == PPS) ? 16'b0000_0000_1000_0000 :
(gp_code == POWER_UP0) ? 16'b0000000_0_0000_0_01_1 :
(gp_code == POWER_UP2) ? 16'b0000000_0_0000_0_01_1 :
(mdal <= HIMEM) ? ram_rdata : 16'bz;
RAM の読み書き
logic init;
assign led1 = init;
ram u_ram(
.*
);
assign cont_n = 1'b0;
logic [21:0] ram_addr;
logic [15:0] ram_rdata;
logic [15:0] ram_wdata;
logic ram_read;
logic ram_write;
logic ram_byte;
always_ff@(posedge clk_x3) begin
if ((mbs == BS_MEM) && (count == 2)) begin
ram_addr <= mdal;
if ((maio[3:2] == 2'b10) || (maio == REQUEST_READ)) begin
miss_n <= 1'b0;
ram_read <= 1'b1;
end
end else if (count == 0) begin
miss_n <= 1'b1;
ram_read <= 1'b0;
end
end
always_ff@(posedge clk) begin
if ((mbs == BS_MEM) && (maio[3:2] == 2'b00) && (!sctl_n)) begin
if (maio == BYTE_WRITE) begin
ram_byte <= 1'b1;
end
ram_write <= 1'b1;
ram_wdata <= dal[15:0];
end else begin
ram_write <= 1'b0;
ram_byte <= 1'b0;
end
end
endmodule
`default_nettype wire
参考文献
DCJ11 Microprocessor User's Guide, 5.3 CONSOLE ODT, 1983
Apple II Reference Manual, Signal description for peripheral I/O, 1978