up
minipcia simple CPLD-powered PCI card

Verilog Source

Disclaimer

I'm not an expert on PCI, nor am I an experienced Verilog user. My code probably looks nothing like what an experienced core designer would create. It's likely that the PCI spec is violated (and with a slow CPLD, almost certain). No PAR#ity is generated. I welcome constructive criticism. I'd be very interested to know what back-side interfaces people graft on to this to drive PICs or other devices.

Space Savings

Properly implementing burst reads and writes was a fun exercise, but a lot of space could probably be saved by using STOP# to terminate transactions after a single data phase. Certain assumptions can be made for single cycle reads and writes and the incrementer for address can be eliminated.

Any place there are fewer different bits in the hardwired register space probably helps cut down on the number of product terms. Making the SUBSYSTEM_ID and VENDOR 0x0000, for example, or making them overlap the DEVICE_ID and VENDOR_ID significantly would help.

Entire states could be removed or simplified. If you don't need writes all that's required is to ignore CFGWRITE (going through the motions with DEVSEL# and TRDY#), and if there's a memory window the same applies to MEMWRITE. If you want to cheat and control the device with config cycles (which is discouraged by the spec, and may be very slow and/or inconvenient in the driver) you could eliminate memory window support and MEMREAD/MEMWRITE altogether.

pci.v

module pci(reset,clk,frame,irdy,trdy,devsel,idsel,ad,cbe,par,stop,inta,led_out);
    input reset;
    input clk;
    input frame;
    input irdy;
    output trdy;
    output devsel;
    input idsel;
    inout [31:0] ad;
    input [3:0] cbe;
    inout par;
    output stop;
    output inta;
    output [3:0] led_out;

parameter DEVICE_ID = 16'h9500;
parameter VENDOR_ID = 16'h106d;		// Sequent!
parameter DEVICE_CLASS = 24'hFF0000;	// Misc
parameter DEVICE_REV = 8'h01;
parameter SUBSYSTEM_ID = 16'h0001;	// Card identifier
parameter SUBSYSTEM_VENDOR_ID = 16'hBEBE; // Card identifier
parameter DEVSEL_TIMING = 2'b00;	// Fast!

reg [2:0] state;
reg [31:0] data;

reg [1:0] enable;
parameter EN_NONE = 0;
parameter EN_RD = 1;
parameter EN_WR = 2;
parameter EN_TR = 3;

reg memen; // respond to baseaddr?
reg [7:0] baseaddr;
reg [5:0] address;

parameter ST_IDLE = 3'b000;
parameter ST_BUSY = 3'b010;
parameter ST_MEMREAD = 3'b100;
parameter ST_MEMWRITE = 3'b101;
parameter ST_CFGREAD = 3'b110;
parameter ST_CFGWRITE = 3'b111;

parameter MEMREAD = 4'b0110;
parameter MEMWRITE = 4'b0111;
parameter CFGREAD = 4'b1010;
parameter CFGWRITE = 4'b1011;

`define LED
`ifdef LED
reg [3:0] led;
`endif

`undef STATE_DEBUG_LED
`ifdef STATE_DEBUG_LED
assign led_out = ~state;
`else
`ifdef LED
assign led_out = ~led;  // board is wired for active low LEDs
`endif
`endif

assign ad = (enable == EN_RD) ? data : 32'bZ;
assign trdy = (enable == EN_NONE) ? 'bZ : (enable == EN_TR ? 1 : 0);
assign par = (enable == EN_RD) ? 0 : 'bZ;
reg devsel;

assign stop = 1'bZ;
assign inta = 1'bZ;

wire cfg_hit = ((cbe == CFGREAD || cbe == CFGWRITE) && idsel && ad[1:0] == 2'b00);
wire addr_hit = ((cbe == MEMREAD || cbe == MEMWRITE) && memen && ad[31:12] == {12'b0, baseaddr});
wire hit = cfg_hit | addr_hit;

always @(posedge clk)
begin
    if (~reset) begin
        state <= ST_IDLE;
        enable <= EN_NONE;
        baseaddr <= 0;
        devsel <= 'bZ;
        memen <= 0;
`ifdef LED
        led <= 0;
`endif
    end
    else    begin
                
    case (state)
        ST_IDLE: begin
            enable <= EN_NONE;
            devsel <= 'bZ;
            if (~frame) begin
                address <= ad[7:2];
                if (hit) begin
                    state <= {1'b1, cbe[3], cbe[0]};
                    devsel <= 0;
                    // pipeline the write enable
                    if (cbe[0])
                        enable <= EN_WR;
                end
                else begin
                    state <= ST_BUSY;
                    enable <= EN_NONE;
                end
            end
        end

        ST_BUSY: begin
            devsel <= 'bZ;
            enable <= EN_NONE;
            if (frame)
                state <= ST_IDLE;
        end

        ST_CFGREAD: begin
            enable <= EN_RD;
            if (~irdy || trdy) begin
                case (address)
                    0: data <= { DEVICE_ID, VENDOR_ID };
                    1: data <= { 5'b0, DEVSEL_TIMING, 9'b0,  14'b0, memen, 1'b0};
                    2: data <= { DEVICE_CLASS, DEVICE_REV };
                    4: data <= { 12'b0, baseaddr, 8'b0, 4'b0010 }; // baseaddr + request mem < 1Mbyte
                    11: data <= {SUBSYSTEM_ID, SUBSYSTEM_VENDOR_ID };
                    16: data <= { 24'b0, baseaddr };
                    default: data <= 'h00000000;
                endcase
                address <= address + 1;
            end
            if (frame && ~irdy && ~trdy) begin
                devsel <= 1;
                state <= ST_IDLE;
                enable <= EN_TR;
            end
        end

        ST_CFGWRITE: begin
            enable <= EN_WR;
            if (~irdy) begin
                case (address)
                    4: baseaddr <= ad[19:12];  // XXX examine cbe
                    1: memen <= ad[1];
                    default: ;
                endcase
                address <= address + 1;
                if (frame) begin
                    devsel <= 1;
                    state <= ST_IDLE;
                    enable <= EN_TR;
                end
            end
        end

        ST_MEMREAD: begin
            enable <= EN_RD;
            if (~irdy || trdy) begin
                case (address)
`ifdef LED
                    0: data <= { 28'b0, led };
`endif
                    default: data <= 'h00000000;
                endcase
                address <= address + 1;
            end
            if (frame && ~irdy && ~trdy) begin
                devsel <= 1;
                state <= ST_IDLE;
                enable <= EN_TR;
            end
        end

        ST_MEMWRITE: begin
            enable <= EN_WR;
            if (~irdy) begin
                case (address)
`ifdef LED
                    0: led <= ad[3:0];
`endif
                    default: ;
                endcase
                address <= address + 1;
                if (frame) begin
                    devsel <= 1;
                    state <= ST_IDLE;
                    enable <= EN_TR;
                end
            end
        end

    endcase
    end
end
endmodule

Testbench

The testbench is left as an exercise for the reader.

pci.ucf

Here's my constraints file. Unless you happen to lay your board out identically to mine yours will be significantly different. My board has the CPLD on the "B" side with pin 1 toward the fingers, roughly centered. I made CLK a wire and routed everything else to avoid crossings that would require extra vias. GCK and GSR need to map to CLK and RESET# but other than that the other signals can go anywhere:

NET "clk" TNM_NET = "clk";
TIMESPEC "TS_clk" = PERIOD "clk" 30 ns HIGH 50 %;
#PACE: Start of Constraints generated by PACE
#PACE: Start of PACE I/O Pin Assignments
NET "ad<0>" LOC = "P32";
NET "ad<10>" LOC = "P13";
NET "ad<11>" LOC = "P18";
NET "ad<12>" LOC = "P11";
NET "ad<13>" LOC = "P17";
NET "ad<14>" LOC = "P10";
NET "ad<15>" LOC = "P61";
NET "ad<16>" LOC = "P67";
NET "ad<17>" LOC = "P3";
NET "ad<18>" LOC = "P68";
NET "ad<19>" LOC = "P2";
NET "ad<1>" LOC = "P25";
NET "ad<20>" LOC = "P69";
NET "ad<21>" LOC = "P1";
NET "ad<22>" LOC = "P70";
NET "ad<23>" LOC = "P84";
NET "ad<24>" LOC = "P72";
NET "ad<25>" LOC = "P82";
NET "ad<26>" LOC = "P77";
NET "ad<27>" LOC = "P81";
NET "ad<28>" LOC = "P76";
NET "ad<29>" LOC = "P80";
NET "ad<2>" LOC = "P31";
NET "ad<30>" LOC = "P75";
NET "ad<31>" LOC = "P79";
NET "ad<3>" LOC = "P24";
NET "ad<4>" LOC = "P26";
NET "ad<5>" LOC = "P23";
NET "ad<6>" LOC = "P21";
NET "ad<7>" LOC = "P15";
NET "ad<8>" LOC = "P14";
NET "ad<9>" LOC = "P19";
NET "cbe<0>" LOC = "P20";
NET "cbe<1>" LOC = "P9";
NET "cbe<2>" LOC = "P4";
NET "cbe<3>" LOC = "P83";
NET "clk" LOC = "P12";
NET "devsel" LOC = "P6";
NET "frame" LOC = "P66";
NET "idsel" LOC = "P71";
NET "inta" LOC = "P58";
NET "irdy" LOC = "P5";
NET "led_out<0>" LOC = "P36";
NET "led_out<1>" LOC = "P35";
NET "led_out<2>" LOC = "P34";
NET "led_out<3>" LOC = "P33";
NET "par" LOC = "P62";
NET "reset" LOC = "P74";
NET "stop" LOC = "P63";
NET "trdy" LOC = "P65";
#PACE: Start of PACE Area Constraints
#PACE: Start of PACE Prohibit Constraints
#PACE: End of Constraints generated by PACE
OFFSET = OUT 23 ns AFTER "clk"  ;
TIMESPEC "TS_P2P" = FROM "PADS" TO "PADS" 23 ns;
project topBen Jackson