PIPO Shift Register
Complete Parallel-In Parallel-Out (PIPO) shift register — four implementations: basic 4-bit, with clock enable, bidirectional, and universal (all four shift register modes in one) — with function tables, circuit diagrams, waveforms, and an exhaustive self-checking testbench.
➡ Introduction & Shift Register Types
A shift register is a cascade of flip-flops where data propagates from one stage to the next on each clock edge. Shift registers are classified by how data enters and exits — serially (one bit at a time) or in parallel (all bits simultaneously). PIPO is the simplest mode: all bits load at once and all bits read out at once.
📋 PIPO Theory & Operation
In PIPO mode, a load signal gates the parallel input data into all flip-flops simultaneously. Between loads, the register holds its current value. Optionally, a shift enable can move data left or right through the flip-flop chain.
Function Table (4-bit PIPO with shift)
| rst_n | load | shift_en | dir | Q[3:0] (next) | Operation |
|---|---|---|---|---|---|
| 0 | x | x | x | 0000 | Synchronous reset |
| 1 | 1 | x | x | d_in[3:0] | Parallel load (PIPO) |
| 1 | 0 | 1 | 1 | {Q[2:0],0} | Shift left (MSB←LSB direction) |
| 1 | 0 | 1 | 0 | {0,Q[3:1]} | Shift right (LSB→MSB direction) |
| 1 | 0 | 0 | x | Q[3:0] | Hold — no change |
PIPO as a Parallel Register (Hold Mode)
In PIPO mode each DFF receives its corresponding d_in bit directly (not chained). In shift mode, each DFF receives its neighbour’s output. The load signal selects between these two paths using a MUX at each DFF’s D input.
🔌 Circuit Diagram
⚫ Implementation 1 — Basic PIPO
The simplest PIPO: synchronous reset, parallel load. On each rising clock edge, when load=1 the entire input word d_in is captured into the register. Between loads the register holds its value.
// ============================================================ // Module : pipo_basic // Function : 4-bit Parallel-In Parallel-Out register // Operation: load=1 -> q <= d_in (parallel capture) // load=0 -> q holds (no change) // ============================================================ `timescale 1ns/1ps `default_nettype none module pipo_basic ( input clk, input rst_n, // synchronous active-low reset input load, // 1 = capture d_in, 0 = hold input [3:0] d_in, // parallel data input output reg [3:0] q // parallel data output ); always @(posedge clk) begin if (!rst_n) q <= 4'b0000; // synchronous reset else if (load) q <= d_in; // parallel load // else: hold (implicit) end endmodule `default_nettype wire
🔵 Implementation 2 — With Clock Enable and Shift
Extends the basic PIPO with a clock enable and a shift-right capability. When load=0 and shift_en=1, data shifts one position right each cycle. The ser_in pin feeds the vacated MSB position.
// ============================================================ // Module : pipo_shift // Priority : rst_n > load > shift_en > hold // Shift : right-shift: q <= {ser_in, q[3:1]} // ============================================================ `timescale 1ns/1ps `default_nettype none module pipo_shift ( input clk, input rst_n, input load, // parallel load (priority over shift) input shift_en, // enable shift when load=0 input ser_in, // serial input (fills MSB on right-shift) input [3:0] d_in, output reg [3:0] q, output ser_out // serial output = LSB shifted out ); always @(posedge clk) begin if (!rst_n) q <= 4'b0; else if (load) q <= d_in; // parallel load else if (shift_en) q <= {ser_in, q[3:1]}; // shift right // else: hold end assign ser_out = q[0]; // LSB is the serial output endmodule `default_nettype wire
Cycle Load d_in ser_in q[3:0] ser_out
0 1 1011 x 1011 1
1 0 - 0 0101 1 <- shift right: 0|101[1]
2 0 - 0 0010 1 <- shift right: 0|010[1]
3 0 - 1 1001 0 <- shift right: 1|001[0]
4 0 - 0 0100 1 <- shift right: 0|100[1]
🟠 Implementation 3 — Bidirectional PIPO
Adds left-shift capability alongside right-shift, controlled by the dir signal. This creates a complete bidirectional shift register that can shift in either direction, load in parallel, or hold.
// ============================================================ // Module : pipo_bidir // dir=1: shift left q <= {q[2:0], ser_in_l} MSB exits // dir=0: shift right q <= {ser_in_r, q[3:1]} LSB exits // ============================================================ `timescale 1ns/1ps `default_nettype none module pipo_bidir ( input clk, rst_n, input load, // parallel load input shift_en, // enable shift input dir, // 1=left, 0=right input ser_in_l, // serial input for left-shift (fills LSB) input ser_in_r, // serial input for right-shift (fills MSB) input [3:0] d_in, output reg [3:0] q, output ser_out_l, // MSB exits on left-shift output ser_out_r // LSB exits on right-shift ); always @(posedge clk) begin if (!rst_n) q <= 4'b0; else if (load) q <= d_in; else if (shift_en) begin if (dir) q <= {q[2:0], ser_in_l}; // shift left: LSB<-ser_in_l else q <= {ser_in_r, q[3:1]}; // shift right: MSB<-ser_in_r end end assign ser_out_l = q[3]; // MSB exits on left-shift assign ser_out_r = q[0]; // LSB exits on right-shift endmodule `default_nettype wire
🟣 Implementation 4 — Universal Shift Register
The universal shift register implements all four modes controlled by a 2-bit mode select. It covers PIPO (load), SISO shift-right, SISO shift-left, and hold — making it usable as any of the four shift register types.
// ============================================================ // Module : pipo_universal // mode[1:0] encoding: // 00 = HOLD (no change) // 01 = SHIFT RIGHT (ser_in -> MSB, LSB -> ser_out) // 10 = SHIFT LEFT (ser_in -> LSB, MSB -> ser_out) // 11 = PARALLEL LOAD (d_in -> q) // ============================================================ `timescale 1ns/1ps `default_nettype none module pipo_universal #(parameter N = 4) ( input clk, rst_n, input [1:0] mode, // 00=hold 01=shr 10=shl 11=load input ser_in, // serial input (used by both shift modes) input [N-1:0] d_in, output reg [N-1:0] q, output ser_out // MSB on left-shift, LSB on right-shift ); localparam [1:0] HOLD = 2'b00, SHR = 2'b01, // shift right SHL = 2'b10, // shift left LOAD = 2'b11; // parallel load always @(posedge clk) begin if (!rst_n) q <= {N{1'b0}}; else case (mode) HOLD: ; // no change SHR : q <= {ser_in, q[N-1:1]}; // right: ser_in fills MSB SHL : q <= {q[N-2:0], ser_in}; // left: ser_in fills LSB LOAD: q <= d_in; // parallel load endcase end // ser_out follows MSB for left-shift, LSB for right-shift assign ser_out = (mode == SHL) ? q[N-1] : q[0]; endmodule `default_nettype wire
🧪 Comprehensive Testbench
The testbench verifies all four implementations simultaneously. Key tests: parallel load captures the correct word, shift-right moves bits correctly with ser_in, shift-left reverses direction, hold freezes the register, and the universal mode register correctly switches between all four modes.
// ============================================================ // Testbench : pipo_tb // DUTs : pipo_basic, pipo_shift, pipo_bidir, pipo_universal // Tests : Parallel load, shift-right, shift-left, // hold, mode switch, reset, serial I/O // ============================================================ `timescale 1ns/1ps `default_nettype none module pipo_tb; reg clk=0, rst_n=1, load=0, shift_en=0, dir=0, ser_in=0; reg [3:0] d_in=0; reg [1:0] mode=2'b11; wire [3:0] q_basic, q_shift, q_bidir, q_univ; wire sout_shift, sout_univ, sout_l, sout_r; pipo_basic u_b (.clk(clk),.rst_n(rst_n),.load(load),.d_in(d_in),.q(q_basic)); pipo_shift u_s (.clk(clk),.rst_n(rst_n),.load(load),.shift_en(shift_en),.ser_in(ser_in),.d_in(d_in),.q(q_shift),.ser_out(sout_shift)); pipo_bidir u_bd (.clk(clk),.rst_n(rst_n),.load(load),.shift_en(shift_en),.dir(dir),.ser_in_l(ser_in),.ser_in_r(ser_in),.d_in(d_in),.q(q_bidir),.ser_out_l(sout_l),.ser_out_r(sout_r)); pipo_universal #(.N(4)) u_u (.clk(clk),.rst_n(rst_n),.mode(mode),.ser_in(ser_in),.d_in(d_in),.q(q_univ),.ser_out(sout_univ)); always #5 clk = ~clk; initial begin $dumpfile("pipo.vcd"); $dumpvars(0,pipo_tb); end integer pass_cnt=0, fail_cnt=0, test_num=0; task tick; @(posedge clk); #1; endtask task check_all; input [3:0] exp; input [255:0] msg; begin test_num++; if(q_basic===exp && q_shift===exp && q_bidir===exp && q_univ===exp) begin $display(" PASS [%2d] %s | q=%04b",test_num,msg,q_basic); pass_cnt++; end else begin $display(" FAIL [%2d] %s | basic=%04b shift=%04b bidir=%04b univ=%04b exp=%04b", test_num,msg,q_basic,q_shift,q_bidir,q_univ,exp); fail_cnt++; end end endtask task check_shift; input [3:0] exp_shift, exp_univ; input [255:0] msg; begin test_num++; if(q_shift===exp_shift && q_univ===exp_univ) begin $display(" PASS [%2d] %s | q_shift=%04b q_univ=%04b",test_num,msg,q_shift,q_univ); pass_cnt++; end else begin $display(" FAIL [%2d] %s | shift=%04b univ=%04b exp_s=%04b exp_u=%04b", test_num,msg,q_shift,q_univ,exp_shift,exp_univ); fail_cnt++; end end endtask initial begin $display("\n======================================================"); $display(" PIPO Shift Register Testbench"); $display("======================================================"); // Reset $display("\n --- Phase 1: Reset ---"); rst_n=0; mode=2'b11; tick; check_all(4'b0000, "Reset -> q=0000"); // Parallel load $display("\n --- Phase 2: Parallel Load ---"); rst_n=1; load=1; mode=2'b11; d_in=4'b1011; tick; check_all(4'b1011, "Load 1011"); d_in=4'b1100; tick; check_all(4'b1100, "Load 1100"); d_in=4'b0000; tick; check_all(4'b0000, "Load 0000"); d_in=4'b1111; tick; check_all(4'b1111, "Load 1111"); // Hold $display("\n --- Phase 3: Hold (load=0, shift=0) ---"); load=0; shift_en=0; mode=2'b00; tick; check_all(4'b1111, "Hold: q unchanged"); tick; check_all(4'b1111, "Hold: q unchanged"); // Shift right: load 1011 then shift right 4 times with ser_in=0 $display("\n --- Phase 4: Right Shift (ser_in=0) ---"); load=1; mode=2'b11; d_in=4'b1011; tick; load=0; shift_en=1; mode=2'b01; ser_in=0; tick; check_shift(4'b0101,4'b0101,"SHR 1011->0101"); tick; check_shift(4'b0010,4'b0010,"SHR 0101->0010"); tick; check_shift(4'b0001,4'b0001,"SHR 0010->0001"); tick; check_shift(4'b0000,4'b0000,"SHR 0001->0000"); // Shift right with ser_in=1 $display("\n --- Phase 5: Right Shift (ser_in=1) ---"); load=1; mode=2'b11; d_in=4'b0000; tick; load=0; shift_en=1; mode=2'b01; ser_in=1; tick; check_shift(4'b1000,4'b1000,"SHR(ser=1) 0000->1000"); tick; check_shift(4'b1100,4'b1100,"SHR(ser=1) 1000->1100"); tick; check_shift(4'b1110,4'b1110,"SHR(ser=1) 1100->1110"); tick; check_shift(4'b1111,4'b1111,"SHR(ser=1) 1110->1111"); // Shift left $display("\n --- Phase 6: Left Shift (ser_in=0) ---"); load=1; mode=2'b11; d_in=4'b1011; tick; load=0; shift_en=1; dir=1; mode=2'b10; ser_in=0; tick; check_shift(4'b0110,4'b0110,"SHL 1011->0110"); tick; check_shift(4'b1100,4'b1100,"SHL 0110->1100"); tick; check_shift(4'b1000,4'b1000,"SHL 1100->1000"); tick; check_shift(4'b0000,4'b0000,"SHL 1000->0000"); // Universal mode switch $display("\n --- Phase 7: Universal mode switching ---"); mode=2'b11; d_in=4'b1010; tick; test_num++; if(q_univ===4'b1010) begin $display(" PASS [%2d] UNIV LOAD 1010",test_num); pass_cnt++; end else begin $display(" FAIL UNIV LOAD"); fail_cnt++; end mode=2'b00; tick; test_num++; if(q_univ===4'b1010) begin $display(" PASS [%2d] UNIV HOLD",test_num); pass_cnt++; end else begin $display(" FAIL UNIV HOLD"); fail_cnt++; end mode=2'b01; ser_in=1; tick; test_num++; if(q_univ===4'b1101) begin $display(" PASS [%2d] UNIV SHR 1010->1101",test_num); pass_cnt++; end else begin $display(" FAIL UNIV SHR"); fail_cnt++; end mode=2'b10; ser_in=0; tick; test_num++; if(q_univ===4'b1010) begin $display(" PASS [%2d] UNIV SHL 1101->1010",test_num); pass_cnt++; end else begin $display(" FAIL UNIV SHL"); fail_cnt++; end $display("\n======================================================"); $display(" RESULTS: %0d / %0d PASS | %0d FAIL",pass_cnt,test_num,fail_cnt); $display("======================================================"); if(fail_cnt==0) $display(" ALL TESTS PASSED\n"); else $fatal(1," %0d FAILURE(S)\n",fail_cnt); #20; $finish; end endmodule `default_nettype wire
📈 Simulation Waveform
💻 Simulation Console Output
How to Run
# Icarus Verilog iverilog -o pipo_sim \ pipo_basic.v pipo_shift.v pipo_bidir.v pipo_universal.v pipo_tb.v vvp pipo_sim gtkwave pipo.vcd # ModelSim vlog pipo_basic.v pipo_shift.v pipo_bidir.v pipo_universal.v pipo_tb.v vsim -c pipo_tb -do "run -all; quit -f"
🔬 Design Analysis & Comparison
Shift Register Type Comparison
| Type | Input mode | Output mode | Latency | Typical use |
|---|---|---|---|---|
| PIPO | Parallel (N bits/cycle) | Parallel (N bits, same cycle) | 1 cycle | Pipeline register, bus latch, data alignment |
| SISO | Serial (1 bit/cycle) | Serial (1 bit/cycle) | N cycles | Delay line, CDC retiming, serial link buffering |
| SIPO | Serial (1 bit/cycle) | Parallel (N bits) | N cycles | Serial-to-parallel converter, UART RX, SPI RX |
| PISO | Parallel (N bits) | Serial (1 bit/cycle) | N cycles | Parallel-to-serial converter, UART TX, SPI TX |
Implementation Feature Matrix
| Module | Width | Load | Shift-R | Shift-L | Hold | Ser I/O |
|---|---|---|---|---|---|---|
| pipo_basic | 4 | Yes | No | No | Yes | No |
| pipo_shift | 4 | Yes | Yes | No | Yes | ser_in/out |
| pipo_bidir | 4 | Yes | Yes | Yes | Yes | L+R pins |
| pipo_universal | N | Yes | Yes | Yes | Yes | ser_in/out |
PIPO as Pipeline Stage
// Two-stage pipeline using PIPO: // Stage 1 computes, Stage 2 registers wire [7:0] stage1_out; reg [7:0] stage2_reg; // stage1_out = combinational logic assign stage1_out = a + b; // PIPO register captures result each cycle always @(posedge clk) stage2_reg <= stage1_out; // pipo_basic
Universal Register as PISO
// Load word, then shift out serially: // Acts as PISO (parallel-in serial-out) // Cycle 0: load=11, d_in=1011 // Cycles 1-4: mode=01 (shift right) // ser_out sequence: 1, 1, 0, 1 (LSB first) // This implements UART TX: // Load byte, shift out LSB-first at baud rate always @(posedge baud_clk) mode <= (load_byte) ? 2'b11 : 2'b01;
always @(posedge clk) q <= d;) is equivalent to a PIPO with load permanently tied high. Adding the load control allows the register to hold its current value when load=0, enabling conditional update — the fundamental mechanism behind clock enables, write enables in register files, and pipeline stalls (hazard freezing). Every control register, pipeline stage, and CSR (Control and Status Register) in a processor is a PIPO register with a qualified write enable.
