「CPUの創りかた」掲載の4bit CPU TD4のMAX II CPLDへの実装方法

「CPUの創りかた」購入から9年を経て,ようやくCPLDにTD4(4bit CPU的なもの)を実装したのでメモしておきます.

経過

本が出版された当初に最後まで読んで,ディスクリートで組むぞーとICを買ってきたのは良いものの,配線する元気が出ずそのままになっていました.4年くらいして,CPLDで組むぞーと,MAX IIマイクロ開発キット(~8k円)を輸入してみたものの,Quartus IIをダウンロードするのに時間がかかりすぎて飽きる.先日,そろそろやるかとQuartus IIをインストールし,Verilog HDLの書き方を覚える(週末の一日).本のTD4仕様書(回路図やら真理値表)をみながらコーディングする(昨日一晩).デバッグ(今晩).やり始めて見るとあっという間にできました.

画像

f:id:beiz23:20131122010900j:image:w250
LEDチカチカ中.

感想

HDL/CPLDを使うと配線変更やシミュレーションができるので,ディスクリート部品を配線するのよりずっと楽に思われた.MAX II(の高いやつ)は2210LEまで入るので,もっと複雑な回路も載せられそう.オプティマイズさんで売っているMAX IIMAX2 CPLDボードは,570LE, 1600円(当時)と小さめではあるが,今回の用途にはそっちでも十分だった気がする.USB接続JTAGケーブルが必要であるが,AVRの書き込み用のものを持っているので.最近はAltera DE0という開発キットも出ていて,こちらはFPGAでもっとたくさんのロジックが入るみたいだけど,そのぶん高価である.
今まで大きめのデータを扱うときは,USB1.0 FullSpeedを介してPCからデジタル回路へ転送していたので,どうしても間に合わないことがあったり,AVRではピンがたりないということがあったりしましたが,CPLDなら,外部メモリを接続できるようにして,そこから読んで,多ピンで出力ということなどが高速にできて良さそう.液晶のドライブとか.

コード

他のところであげられているものの方が洗練されていると思われます.
HDLならば論理合成は計算機がやってくれますが,私は本の通り,論理合成後のものをなるべく使って書いてみました.
50LEでした.そのうち半分くらいはクロック分周用..


module td4_mb(
    input CLOCK_50,
    input [3:0] KEY,
    output [7:0] LED
);
    wire [3:0] rom_addr;
    wire [7:0] rom_data;
    reg [24:0] counter;

    always @( posedge CLOCK_50 ) begin
        counter <= counter + 1'b1;
    end

    assign LED[7:4] = 4'b1111;
    td4 td4(counter[24], KEY[0], 4'b0, LED[3:0], rom_addr, rom_data);

    ROM rom (rom_addr, rom_data);

endmodule

module td4(
input CK, CLRB,
input [3:0] In,
output [3:0] Out, ADDR,
input [7:0] rom_data
);
    //pp.201, 215
    wire [3:0] Y, SIGMA;
    wire [3:0] C0, C1;
    wire [3:0] Op, Im;
    wire [3:0] LOADB;
    wire [1:0] SEL;
    wire CARRY, CF;

    assign Op = rom_data[7:4];
    assign Im = rom_data[3:0];

    SELECTER4 sel4 (C0, C1, In, 4'b0, SEL, Y);

    FULL_ADDR4 alu (Y, Im, SIGMA, CARRY);

    COUNTER4 rega (SIGMA, CK, CLRB, LOADB[0], 1'b0, C0);
    COUNTER4 regb (SIGMA, CK, CLRB, LOADB[1], 1'b0, C1);
    //p.222
    COUNTER4 regc (SIGMA, CK, CLRB, LOADB[2], 1'b0, Out);
    //C3->gomi p.206->address p.215
    COUNTER4 counter (SIGMA, CK, CLRB, LOADB[3], 1'b1, ADDR);

    //p.210
    FLAG flag (CK, CLRB, CARRY, CF);

    //p.272
    DECODER dec (Op, CF, SEL, LOADB);

endmodule

module ROM(
    input [3:0] ADDR,
    output [7:0] DATA
);
    //p.296
    function [7:0] romout;
    input [3:0] ADDR;
        case (ADDR)
            4'd0 :  romout = 8'b1011_1100;
            4'd1 :  romout = 8'b1011_1001;
            4'd2 :  romout = 8'b1011_0011;
            4'd3 :  romout = 8'b1011_0111;
            4'd4 :  romout = 8'b1011_0111;
            4'd5 :  romout = 8'b1011_0011;
            4'd6 :  romout = 8'b1011_1001;
            4'd7 :  romout = 8'b1011_1100;
            4'd8 :  romout = 8'b1011_1110;
            4'd9 :  romout = 8'b1111_0000;
            4'd10:  romout = 8'h00;
            4'd11:  romout = 8'h00;
            4'd12:  romout = 8'h00;
            4'd13:  romout = 8'h00;
            4'd14:  romout = 8'h00;
            4'd15:  romout = 8'h00;
            default:    romout = 8'hxx;
        endcase
    endfunction
    assign DATA = romout(ADDR);

endmodule

module COUNTER4 (
    input [3:0] A,
    input CK, CLRB, LOADB, ENTENP,
    output reg [3:0] QA
);
    //74HC161
    //p.188
    //p.214
    always @ (posedge CK or negedge CLRB) begin
        if (!CLRB)
            QA <= 4'b0;
        else if (!LOADB)
            QA <= A;
        else if (ENTENP)
            QA <= QA + 1'b1;
    end

endmodule

module FULL_ADDR (
    input CIN, A, B,
    output S, C
);
    //p.198 full_addr
    assign C = (A & B) | (B & CIN) | (CIN & A);
    assign S = A ^ B ^ CIN;
    //assign {C, S} = A + B + CIN;

endmodule

module FULL_ADDR4 (
    input [3:0] a, b,
    //input cin,
    output [3:0] q,
    output cout
);
    //p.199
    wire [2:0] c;

    FULL_ADDR add0 (1'b0, a[0], b[0], q[0], c[0]);
    FULL_ADDR add1 (c[0], a[1], b[1], q[1], c[1]);
    FULL_ADDR add2 (c[1], a[2], b[2], q[2], c[2]);
    FULL_ADDR add3 (c[2], a[3], b[3], q[3], cout);

endmodule

module SELECTER4 (
    input [3:0] C0, C1, C2, C3,
    input [1:0] A,
    output [3:0] Y
);
    //p.177
    function [3:0] select;
    input [3:0] C0, C1, C2, C3;
    input [1:0] A;
        case ( A )
            2'b00:  select = C0;
            2'b01:  select = C1;
            2'b10:  select = C2;
            2'b11:  select = C3;
            default:    select = 4'bx;
        endcase
    endfunction

    assign Y = select(C0, C1, C2, C3, A);

endmodule

module FLAG (
input CK, CLRB, D,
output reg Q
);
    //p.210
    always @ (posedge CK or negedge CLRB) begin
        if (CLRB==1'b0)
            Q <= 1'b0;
        else
            Q <= D;
    end

endmodule

module DECODER (
    input [3:0] Op,
    input CF,
    output [1:0] SEL,
    output [3:0] LOADB
);
    //p.272
    assign SEL[0] = Op[0] | Op[3];//SELA
    assign SEL[1] = Op[1]; //SELB
    assign LOADB[0] = Op[2] | Op[3];
    assign LOADB[1] = ~Op[2] | Op[3];
    assign LOADB[2] = ~(~Op[2] & Op[3]);
    assign LOADB[3] = ~((CF | Op[0]) & Op[2] & Op[3]);

endmodule

参考

CPUの創りかた