aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp2/control_lib
diff options
context:
space:
mode:
authorJosh Blum <josh@joshknows.com>2011-11-07 17:09:07 -0800
committerJosh Blum <josh@joshknows.com>2011-11-07 17:09:07 -0800
commit11f1390bbde65c60f45962acb128cac1ce21e474 (patch)
tree372f2e426781de9885889bec6aa98697006268ec /fpga/usrp2/control_lib
parent902818f50bbd486138a7d4cd2ce9ba3661f4a732 (diff)
parentbcb80c5cf9a2117f9d6d22b8e793ea2ecb68ed1f (diff)
downloaduhd-11f1390bbde65c60f45962acb128cac1ce21e474.tar.gz
uhd-11f1390bbde65c60f45962acb128cac1ce21e474.tar.bz2
uhd-11f1390bbde65c60f45962acb128cac1ce21e474.zip
Merge branch 'fpga_master' into uhd_next
Diffstat (limited to 'fpga/usrp2/control_lib')
-rw-r--r--fpga/usrp2/control_lib/Makefile.srcs3
-rw-r--r--fpga/usrp2/control_lib/dbsm.v164
-rw-r--r--fpga/usrp2/control_lib/double_buffer.v139
-rw-r--r--fpga/usrp2/control_lib/double_buffer_tb.v253
-rw-r--r--fpga/usrp2/control_lib/gpio_atr.v71
5 files changed, 630 insertions, 0 deletions
diff --git a/fpga/usrp2/control_lib/Makefile.srcs b/fpga/usrp2/control_lib/Makefile.srcs
index a1c11c026..567feacde 100644
--- a/fpga/usrp2/control_lib/Makefile.srcs
+++ b/fpga/usrp2/control_lib/Makefile.srcs
@@ -11,7 +11,9 @@ atr_controller.v \
bin2gray.v \
dcache.v \
decoder_3_8.v \
+dbsm.v \
dpram32.v \
+double_buffer.v \
gray2bin.v \
gray_send.v \
icache.v \
@@ -51,4 +53,5 @@ nsgpio16LE.v \
settings_bus_16LE.v \
atr_controller16.v \
fifo_to_wb.v \
+gpio_atr.v \
))
diff --git a/fpga/usrp2/control_lib/dbsm.v b/fpga/usrp2/control_lib/dbsm.v
new file mode 100644
index 000000000..fea51096f
--- /dev/null
+++ b/fpga/usrp2/control_lib/dbsm.v
@@ -0,0 +1,164 @@
+//
+// Copyright 2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+module dbsm
+ (input clk,
+ input reset,
+ input clear,
+
+ output write_ok,
+ output write_ptr,
+ input write_done,
+
+ output read_ok,
+ output read_ptr,
+ input read_done,
+
+ output access_ok,
+ output access_ptr,
+ input access_done,
+ input access_skip_read
+ );
+
+ localparam PORT_WAIT_0 = 0;
+ localparam PORT_USE_0 = 1;
+ localparam PORT_WAIT_1 = 2;
+ localparam PORT_USE_1 = 3;
+
+ reg [1:0] write_port_state, access_port_state, read_port_state;
+
+ localparam BUFF_WRITABLE = 0;
+ localparam BUFF_ACCESSIBLE = 1;
+ localparam BUFF_READABLE = 2;
+ localparam BUFF_ERROR = 3;
+
+ wire [1:0] buff_state[0:1];
+
+ always @(posedge clk)
+ if(reset | clear)
+ write_port_state <= PORT_WAIT_0;
+ else
+ case(write_port_state)
+ PORT_WAIT_0 :
+ if(buff_state[0]==BUFF_WRITABLE)
+ write_port_state <= PORT_USE_0;
+ PORT_USE_0 :
+ if(write_done)
+ write_port_state <= PORT_WAIT_1;
+ PORT_WAIT_1 :
+ if(buff_state[1]==BUFF_WRITABLE)
+ write_port_state <= PORT_USE_1;
+ PORT_USE_1 :
+ if(write_done)
+ write_port_state <= PORT_WAIT_0;
+ endcase // case (write_port_state)
+
+ assign write_ok = (write_port_state == PORT_USE_0) | (write_port_state == PORT_USE_1);
+ assign write_ptr = (write_port_state == PORT_USE_1);
+
+ always @(posedge clk)
+ if(reset | clear)
+ access_port_state <= PORT_WAIT_0;
+ else
+ case(access_port_state)
+ PORT_WAIT_0 :
+ if(buff_state[0]==BUFF_ACCESSIBLE)
+ access_port_state <= PORT_USE_0;
+ PORT_USE_0 :
+ if(access_done)
+ access_port_state <= PORT_WAIT_1;
+ PORT_WAIT_1 :
+ if(buff_state[1]==BUFF_ACCESSIBLE)
+ access_port_state <= PORT_USE_1;
+ PORT_USE_1 :
+ if(access_done)
+ access_port_state <= PORT_WAIT_0;
+ endcase // case (access_port_state)
+
+ assign access_ok = (access_port_state == PORT_USE_0) | (access_port_state == PORT_USE_1);
+ assign access_ptr = (access_port_state == PORT_USE_1);
+
+ always @(posedge clk)
+ if(reset | clear)
+ read_port_state <= PORT_WAIT_0;
+ else
+ case(read_port_state)
+ PORT_WAIT_0 :
+ if(buff_state[0]==BUFF_READABLE)
+ read_port_state <= PORT_USE_0;
+ PORT_USE_0 :
+ if(read_done)
+ read_port_state <= PORT_WAIT_1;
+ PORT_WAIT_1 :
+ if(buff_state[1]==BUFF_READABLE)
+ read_port_state <= PORT_USE_1;
+ PORT_USE_1 :
+ if(read_done)
+ read_port_state <= PORT_WAIT_0;
+ endcase // case (read_port_state)
+
+ assign read_ok = (read_port_state == PORT_USE_0) | (read_port_state == PORT_USE_1);
+ assign read_ptr = (read_port_state == PORT_USE_1);
+
+ buff_sm #(.PORT_USE_FLAG(PORT_USE_0)) buff0_sm
+ (.clk(clk), .reset(reset), .clear(clear),
+ .write_done(write_done), .access_done(access_done), .access_skip_read(access_skip_read), .read_done(read_done),
+ .write_port_state(write_port_state), .access_port_state(access_port_state), .read_port_state(read_port_state),
+ .buff_state(buff_state[0]));
+
+ buff_sm #(.PORT_USE_FLAG(PORT_USE_1)) buff1_sm
+ (.clk(clk), .reset(reset), .clear(clear),
+ .write_done(write_done), .access_done(access_done), .access_skip_read(access_skip_read), .read_done(read_done),
+ .write_port_state(write_port_state), .access_port_state(access_port_state), .read_port_state(read_port_state),
+ .buff_state(buff_state[1]));
+
+endmodule // dbsm
+
+module buff_sm
+ #(parameter PORT_USE_FLAG=0)
+ (input clk, input reset, input clear,
+ input write_done, input access_done, input access_skip_read, input read_done,
+ input [1:0] write_port_state, input [1:0] access_port_state, input [1:0] read_port_state,
+ output reg [1:0] buff_state);
+
+ localparam BUFF_WRITABLE = 0;
+ localparam BUFF_ACCESSIBLE = 1;
+ localparam BUFF_READABLE = 2;
+ localparam BUFF_ERROR = 3;
+
+ always @(posedge clk)
+ if(reset | clear)
+ buff_state <= BUFF_WRITABLE;
+ else
+ case(buff_state)
+ BUFF_WRITABLE :
+ if(write_done & (write_port_state == PORT_USE_FLAG))
+ buff_state <= BUFF_ACCESSIBLE;
+ BUFF_ACCESSIBLE :
+ if(access_done & (access_port_state == PORT_USE_FLAG))
+ if(access_skip_read)
+ buff_state <= BUFF_WRITABLE;
+ else
+ buff_state <= BUFF_READABLE;
+ BUFF_READABLE :
+ if(read_done & (read_port_state == PORT_USE_FLAG))
+ buff_state <= BUFF_WRITABLE;
+ BUFF_ERROR :
+ ;
+ endcase
+
+endmodule // buff_sm
diff --git a/fpga/usrp2/control_lib/double_buffer.v b/fpga/usrp2/control_lib/double_buffer.v
new file mode 100644
index 000000000..8865bddee
--- /dev/null
+++ b/fpga/usrp2/control_lib/double_buffer.v
@@ -0,0 +1,139 @@
+//
+// Copyright 2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+module double_buffer
+ #(parameter BUF_SIZE = 9)
+ (input clk, input reset, input clear,
+
+ // Random access interface to RAM
+ input access_we,
+ input access_stb,
+ output access_ok,
+ input access_done,
+ input access_skip_read,
+ input [BUF_SIZE-1:0] access_adr,
+ output [BUF_SIZE-1:0] access_len,
+ input [35:0] access_dat_i,
+ output [35:0] access_dat_o,
+
+ // Write FIFO Interface
+ input [35:0] data_i,
+ input src_rdy_i,
+ output dst_rdy_o,
+
+ // Read FIFO Interface
+ output [35:0] data_o,
+ output src_rdy_o,
+ input dst_rdy_i
+ );
+
+ wire [35:0] data_o_0, data_o_1;
+
+ wire read, read_ok, read_ptr, read_done;
+ wire write, write_ok, write_ptr, write_done;
+
+ wire [BUF_SIZE-1:0] rw0_adr, rw1_adr;
+ reg [BUF_SIZE-1:0] read_adr, write_adr;
+ reg [BUF_SIZE-1:0] len0, len1;
+
+ assign data_o = read_ptr ? data_o_1 : data_o_0;
+ assign rw0_adr = (write_ok & ~write_ptr) ? write_adr : read_adr;
+ assign rw1_adr = (write_ok & write_ptr) ? write_adr : read_adr;
+
+ wire [35:0] access_dat_o_0, access_dat_o_1;
+ wire access_ptr;
+ assign access_dat_o = access_ptr? access_dat_o_1 : access_dat_o_0;
+
+ dbsm dbsm
+ (.clk(clk), .reset(reset), .clear(clear),
+ .write_ok(write_ok), .write_ptr(write_ptr), .write_done(write_done),
+ .access_ok(access_ok), .access_ptr(access_ptr), .access_done(access_done), .access_skip_read(access_skip_read),
+ .read_ok(read_ok), .read_ptr(read_ptr), .read_done(read_done));
+
+ // Port A for random access, Port B for FIFO read and write
+ ram_2port #(.DWIDTH(36),.AWIDTH(BUF_SIZE)) buffer0
+ (.clka(clk),.ena(access_stb & access_ok & (access_ptr == 0)),.wea(access_we),
+ .addra(access_adr),.dia(access_dat_i),.doa(access_dat_o_0),
+ .clkb(clk),.enb((read & read_ok & ~read_ptr)|(write & write_ok & ~write_ptr) ),.web(write&write_ok&~write_ptr),
+ .addrb(rw0_adr),.dib(data_i),.dob(data_o_0));
+
+ ram_2port #(.DWIDTH(36),.AWIDTH(BUF_SIZE)) buffer1
+ (.clka(clk),.ena(access_stb & access_ok & (access_ptr == 1)),.wea(access_we),
+ .addra(access_adr),.dia(access_dat_i),.doa(access_dat_o_1),
+ .clkb(clk),.enb((read & read_ok & read_ptr)|(write & write_ok & write_ptr) ),.web(write&write_ok&write_ptr),
+ .addrb(rw1_adr),.dib(data_i),.dob(data_o_1));
+
+ // Write into buffers
+ assign dst_rdy_o = write_ok;
+ assign write = src_rdy_i & write_ok;
+ assign write_done = write & data_i[33]; // done
+ always @(posedge clk)
+ if(reset | clear)
+ write_adr <= 0;
+ else
+ if(write_done)
+ begin
+ write_adr <= 0;
+ if(write_ptr)
+ len1 <= write_adr + 1;
+ else
+ len0 <= write_adr + 1;
+ end
+ else if(write)
+ write_adr <= write_adr + 1;
+
+ assign access_len = access_ptr ? len1 : len0;
+
+ reg [1:0] read_state;
+ localparam IDLE = 0;
+ localparam PRE_READ = 1;
+ localparam READING = 2;
+
+ always @(posedge clk)
+ if(reset | clear)
+ begin
+ read_state <= IDLE;
+ read_adr <= 0;
+ end
+ else
+ case(read_state)
+ IDLE :
+ begin
+ read_adr <= 0;
+ if(read_ok)
+ read_state <= PRE_READ;
+ end
+ PRE_READ :
+ begin
+ read_state <= READING;
+ read_adr <= 1;
+ end
+
+ READING :
+ if(dst_rdy_i)
+ begin
+ read_adr <= read_adr + 1;
+ if(data_o[33])
+ read_state <= IDLE;
+ end
+ endcase // case (read_state)
+
+ assign read = ~((read_state==READING)& ~dst_rdy_i);
+ assign read_done = data_o[33] & dst_rdy_i & src_rdy_o;
+ assign src_rdy_o = (read_state == READING);
+
+endmodule // double_buffer
diff --git a/fpga/usrp2/control_lib/double_buffer_tb.v b/fpga/usrp2/control_lib/double_buffer_tb.v
new file mode 100644
index 000000000..a9aae6956
--- /dev/null
+++ b/fpga/usrp2/control_lib/double_buffer_tb.v
@@ -0,0 +1,253 @@
+//
+// Copyright 2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+module double_buffer_tb();
+
+ reg clk = 0;
+ reg rst = 1;
+ reg clear = 0;
+ initial #1000 rst = 0;
+ always #50 clk = ~clk;
+
+ wire src_rdy_o;
+ reg src_rdy_i = 0;
+ wire dst_rdy_o;
+
+ wire dst_rdy_i = 1;
+ wire [35:0] data_o;
+ reg [35:0] data_i;
+
+ wire access_we, access_stb, access_done, access_ok, access_skip_read;
+
+ wire [8:0] access_adr, access_len;
+ wire [35:0] dsp_to_buf, buf_to_dsp;
+ reg set_stb = 0;
+
+ double_buffer db
+ (.clk(clk),.reset(rst),.clear(0),
+ .access_we(access_we), .access_stb(access_stb), .access_ok(access_ok), .access_done(access_done),
+ .access_skip_read(access_skip_read), .access_adr(access_adr), .access_len(access_len),
+ .access_dat_i(dsp_to_buf), .access_dat_o(buf_to_dsp),
+
+ .data_i(data_i), .src_rdy_i(src_rdy_i), .dst_rdy_o(dst_rdy_o),
+ .data_o(data_o), .src_rdy_o(src_rdy_o), .dst_rdy_i(dst_rdy_i));
+
+ dspengine_16to8 dspengine_16to8
+ (.clk(clk),.reset(rst),.clear(0),
+ .set_stb(set_stb), .set_addr(0), .set_data({13'h0,1'b1,18'h00400}),
+ .access_we(access_we), .access_stb(access_stb), .access_ok(access_ok), .access_done(access_done),
+ .access_skip_read(access_skip_read), .access_adr(access_adr), .access_len(access_len),
+ .access_dat_i(buf_to_dsp), .access_dat_o(dsp_to_buf));
+
+ always @(posedge clk)
+ if(src_rdy_o & dst_rdy_i)
+ begin
+ $display("SOF %d, EOF %d, OCC %x, DAT %x",data_o[32],data_o[33],data_o[35:34],data_o[31:0]);
+ if(data_o[33])
+ $display();
+ end
+ initial $dumpfile("double_buffer_tb.vcd");
+ initial $dumpvars(0,double_buffer_tb);
+
+ initial
+ begin
+ @(negedge rst);
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+
+ // Passthrough
+ $display("Passthrough");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'hFFFFFFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h04050607};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h08090a0b};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h0c0d0e0f};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ repeat (5)
+ @(posedge clk);
+
+ $display("Enabled");
+ set_stb <= 1;
+ @(posedge clk);
+ set_stb <= 0;
+
+ @(posedge clk);
+ $display("Non-IF Data Passthrough");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'hC0000000};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h14151617};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h18191a1b};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h1c1d1e1f};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("No StreamID, No Trailer, Even");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h0000FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h01000200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h03000400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h05000600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h07000800};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h09000a00};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h0b000c00};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("No StreamID, No Trailer, Odd");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h0000FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h11001200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h13001400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h15001600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h17001800};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h19001a00};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("No StreamID, Trailer, Even");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h0400FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h21002200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h23002400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h25002600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h27002800};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h29002a00};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("No StreamID, Trailer, Odd");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h0400FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h31003200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h33003400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h35003600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h39003a00};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("StreamID, No Trailer, Even");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h1000FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h11001200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h13001400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h15001600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'h17001800};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'h19001a00};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ while(~dst_rdy_o)
+ @(posedge clk);
+
+ $display("StreamID, Trailer, Odd");
+ src_rdy_i <= 1;
+ data_i <= { 2'b00,1'b0,1'b1,32'h1400FFFF};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'ha100a200};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'ha300a400};
+ src_rdy_i <= 0;
+ @(posedge clk);
+ src_rdy_i <= 1;
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'ha500a600};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b0,1'b0,32'ha700a800};
+ @(posedge clk);
+ data_i <= { 2'b00,1'b1,1'b0,32'hbbb0bbb0};
+ @(posedge clk);
+ src_rdy_i <= 0;
+ @(posedge clk);
+
+ end
+
+ initial #28000 $finish;
+endmodule // double_buffer_tb
diff --git a/fpga/usrp2/control_lib/gpio_atr.v b/fpga/usrp2/control_lib/gpio_atr.v
new file mode 100644
index 000000000..82d72b815
--- /dev/null
+++ b/fpga/usrp2/control_lib/gpio_atr.v
@@ -0,0 +1,71 @@
+
+//
+// Copyright 2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+module gpio_atr
+ #(parameter BASE = 0,
+ parameter WIDTH = 32)
+ (input clk, input reset,
+ input set_stb, input [7:0] set_addr, input [31:0] set_data,
+ input rx, input tx,
+ inout [WIDTH-1:0] gpio,
+ output reg [31:0] gpio_readback
+ );
+
+ wire [WIDTH-1:0] ddr, in_idle, in_tx, in_rx, in_fdx;
+ reg [WIDTH-1:0] rgpio, igpio;
+
+ setting_reg #(.my_addr(BASE+0), .width(WIDTH)) reg_idle
+ (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data),
+ .out(in_idle),.changed());
+
+ setting_reg #(.my_addr(BASE+1), .width(WIDTH)) reg_rx
+ (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data),
+ .out(in_rx),.changed());
+
+ setting_reg #(.my_addr(BASE+2), .width(WIDTH)) reg_tx
+ (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data),
+ .out(in_tx),.changed());
+
+ setting_reg #(.my_addr(BASE+3), .width(WIDTH)) reg_fdx
+ (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data),
+ .out(in_fdx),.changed());
+
+ setting_reg #(.my_addr(BASE+4), .width(WIDTH)) reg_ddr
+ (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data),
+ .out(ddr),.changed());
+
+ always @(posedge clk)
+ case({tx,rx})
+ 2'b00: rgpio <= in_idle;
+ 2'b01: rgpio <= in_rx;
+ 2'b10: rgpio <= in_tx;
+ 2'b11: rgpio <= in_fdx;
+ endcase // case ({tx,rx})
+
+ integer n;
+ always @*
+ for(n=0;n<WIDTH;n=n+1)
+ igpio[n] <= ddr[n] ? rgpio[n] : 1'bz;
+
+ assign gpio = igpio;
+
+ always @(posedge clk)
+ gpio_readback <= gpio;
+
+endmodule // gpio_atr