Case Study: 74HC195 4-Bit Shift Register
A complete walkthrough of implementing a timing-accurate behavioral model for a standard logic IC.
Overview
This case study demonstrates how to create a production-quality behavioral model by implementing the 74HC195, a 4-bit parallel-access shift register with JK-style serial inputs. This IC is representative of standard logic components that benefit from behavioral modeling.
Component Overview
74HC195 Features
The 74HC195 is a high-speed CMOS 4-bit shift register with:
- Asynchronous master reset (active low) - clears all outputs
- Parallel data loading (active low enable) - loads D0-D3 into Q0-Q3
- Serial shift right with JK-style control inputs
- Complemented output Q̄3 for the MSB
Pin Configuration
| Pin Name | Type | Description |
|---|---|---|
MR |
Input | Master Reset (active low) - asynchronous clear |
J |
Input | Serial input J (JK flip-flop style) |
K̄ |
Input | Serial input K complement |
D0-D3 |
Input | Parallel data inputs |
PE |
Input | Parallel Enable (active low) |
CP |
Input | Clock input (rising edge active) |
Q0-Q3 |
Output | 4-bit register outputs |
Q̄3 |
Output | Complement of Q3 |
Functional Behavior
The 74HC195 operates in three modes, with priority order:
- Asynchronous Reset: When MR=0, all outputs go to 0 (independent of clock)
- Parallel Load: When PE=0 on rising clock edge, load D0-D3 → Q0-Q3
- Shift Right: When PE=1 on rising clock edge, shift data right with JK control:
- J=0, K̄=0: Shift in 0 (reset Q0)
- J=1, K̄=1: Shift in 1 (set Q0)
- J=1, K̄=0: Toggle Q0
- J=0, K̄=1: Hold Q0 (no change)
Timing Specifications (VCC=4.5V)
| Parameter | Symbol | Min | Typ | Max | Units |
|---|---|---|---|---|---|
| Clock pulse width (high) | tpw_h | 20 | - | - | ns |
| Clock pulse width (low) | tpw_l | 20 | - | - | ns |
| Setup time (J, K̄, PE, D) | tsu | 25 | - | - | ns |
| Hold time | th | 3 | - | - | ns |
| MR pulse width | tw_mr | 20 | - | - | ns |
| MR removal time | trec_mr | 20 | - | - | ns |
| Propagation delay CP→Q | tpd | - | 22 | 44 | ns |
| Propagation delay MR→Q | tpd_mr | - | 19 | 38 | ns |
Design Approach
Planning the Model Structure
Before writing code, we need to plan how signals map to the behavioral model interface:
Inputs (9 total)
Index Signal Description
----- ------ -----------
0 MR Master reset (active low)
1 J Serial input J
2 K_bar Serial input K complement
3 D0 Parallel data bit 0
4 D1 Parallel data bit 1
5 D2 Parallel data bit 2
6 D3 Parallel data bit 3
7 PE Parallel enable (active low)
8 CP Clock input
State Variables (5 total)
The model needs state to remember values between evaluations:
Index Variable Purpose
----- --------- -------
9 prev_clk Previous clock value (for edge detection)
10 sq0 Saved Q0 value (internal state)
11 sq1 Saved Q1 value
12 sq2 Saved Q2 value
13 sq3 Saved Q3 value
sq0-sq3 variables store
the primary latch states, which is crucial for proper JK flip-flop operation during hold mode.
Using separate state prevents feedback issues when Q outputs are read during the same evaluation.
Outputs (5 total)
Index Signal Description
----- ------ -----------
0 Q0 Register bit 0 (LSB)
1 Q1 Register bit 1
2 Q2 Register bit 2
3 Q3 Register bit 3 (MSB)
4 Q3_bar Complement of Q3
Timing Checks (10 total)
Index Check Description
----- --------------- -----------
0 tsu_jk Setup time for J and K inputs
1 th_jk Hold time for J and K inputs
2 tsu_pe Setup time for PE input
3 th_pe Hold time for PE input
4 tsu_parallel Setup time for D0-D3 inputs
5 th_parallel Hold time for D0-D3 inputs
6 tpw_clk_high Clock high pulse width
7 tpw_clk_low Clock low pulse width
8 mr_width MR pulse width
9 mr_removal MR removal time before clock edge
Implementation Walkthrough
Netlist Declaration
First, declare the component in your SNL netlist:
Type=hc74195
I=mr,j,k_bar,d0,d1,d2,d3,pe,cp
O=q0,q1,q2,q3,q3_bar
behavioral="simicpy"
state=prev_clk,sq0,sq1,sq2,sq3
checks=tsu_jk,th_jk,tsu_pe,th_pe,tsu_parallel,th_parallel,tpw_clk_high,tpw_clk_low,mr_width,mr_removal
ODEL=44,44,44,44,44
Key attributes:
behavioral="simicpy"- Use Python behavioral model frameworkstate=...- Declares 5 state variables appended to inputschecks=...- Declares 10 timing checks to validateODEL=44,...- Output delays in nanoseconds (from datasheet max tpd)
State Initialization
The init_state() method sets initial values for state variables:
def init_state(self):
"""Initialize state variables to unknown (X)."""
return bytes([LEVEL_X, LEVEL_X, LEVEL_X, LEVEL_X, LEVEL_X])
Core Evaluation Logic
The evalu8() method implements the functional behavior:
1. Extract Inputs and State
# Extract input signal levels (mask to get logic level only)
mr = inputs[0] & LEVEL_FIELD
j = inputs[1] & LEVEL_FIELD
k_bar = inputs[2] & LEVEL_FIELD
d0 = inputs[3] & LEVEL_FIELD
d1 = inputs[4] & LEVEL_FIELD
d2 = inputs[5] & LEVEL_FIELD
d3 = inputs[6] & LEVEL_FIELD
pe = inputs[7] & LEVEL_FIELD
clk = inputs[8] & LEVEL_FIELD
# Extract state variables
prev_clk = inputs[9] & LEVEL_FIELD
sq0 = inputs[10] & LEVEL_FIELD
sq1 = inputs[11] & LEVEL_FIELD
sq2 = inputs[12] & LEVEL_FIELD
sq3 = inputs[13] & LEVEL_FIELD
2. Handle Asynchronous Reset (Highest Priority)
if mr == LEVEL_ZERO:
# Master reset active - clear all outputs and state
out[0] = LEVEL_ZERO # Q0
out[1] = LEVEL_ZERO # Q1
out[2] = LEVEL_ZERO # Q2
out[3] = LEVEL_ZERO # Q3
out[4] = LEVEL_ONE # Q3_bar = NOT(0) = 1
out[5] = clk # Save current clock
out[6] = LEVEL_ZERO # sq0
out[7] = LEVEL_ZERO # sq1
out[8] = LEVEL_ZERO # sq2
out[9] = LEVEL_ZERO # sq3
return bytes(out)
3. Detect Clock Rising Edge
clock_rising = (prev_clk == LEVEL_ZERO and clk == LEVEL_ONE)
4. Parallel Load Mode
if clock_rising and pe == LEVEL_ZERO:
# Load parallel data into outputs and state
out[0] = d0 # Q0
out[1] = d1 # Q1
out[2] = d2 # Q2
out[3] = d3 # Q3
out[6] = d0 # sq0
out[7] = d1 # sq1
out[8] = d2 # sq2
out[9] = d3 # sq3
5. Shift Mode with JK Logic
The JK flip-flop logic for Q0 implements all four operational modes:
if clock_rising and pe == LEVEL_ONE:
# JK flip-flop equation: d = (J & ~Q) | (K̄ & (Q | J))
# This naturally handles:
# J=0, K̄=0: d = 0 (reset)
# J=1, K̄=1: d = 1 (set)
# J=1, K̄=0: d = ~Q (toggle)
# J=0, K̄=1: d = Q (hold)
not_sq0 = notTbl[sq0]
new_sq0 = (j & not_sq0) | (k_bar & (sq0 | j))
# Shift right: new value enters Q0, others shift
out[0] = new_sq0 # Q0 ← JK result
out[1] = sq0 # Q1 ← Q0
out[2] = sq1 # Q2 ← Q1
out[3] = sq2 # Q3 ← Q2
out[6] = new_sq0 # sq0
out[7] = sq0 # sq1
out[8] = sq1 # sq2
out[9] = sq2 # sq3
sq0 (saved state) for feedback,
not out[0] (current output). This prevents circular dependencies during hold operations
where Q must read its own previous value.
6. Hold State (No Clock Edge)
else:
# No edge or falling edge - maintain current values
out[0] = sq0
out[1] = sq1
out[2] = sq2
out[3] = sq3
out[6] = sq0 # State stays the same
out[7] = sq1
out[8] = sq2
out[9] = sq3
7. Complement Output and Clock State
# Q3_bar is always complement of Q3
out[4] = notTbl[out[3]]
# Save current clock for next edge detection
out[5] = clk
Timing Checks Implementation
When timing checks are enabled (ci is not None), validate timing constraints:
Setup Time Check Example
if clock_rising and check_time[0] > 0 and j != LEVEL_X and k_bar != LEVEL_X:
latest_change = max(last_time[PIN_J], last_time[PIN_K_BAR])
if current_time - latest_change < check_time[0]:
errors[0] = "J/K setup time violation before clock rising edge"
Pulse Width Check Example
if clock_falling and check_time[6] > 0:
pulse_width = current_time - last_time[PIN_CP]
if pulse_width < check_time[6]:
errors[6] = "Clock high pulse width too short"
Testing Strategy
Test Scenarios
A comprehensive test suite should cover:
- Reset Test
- Apply MR=0, verify all outputs go to 0
- Verify reset is asynchronous (works without clock)
- Check Q3_bar=1 when Q3=0
- Parallel Load Test
- Set PE=0, load patterns: 0000, 1111, 1010, 0101
- Verify all Q outputs match D inputs after clock edge
- Test multiple consecutive loads
- Shift Operations Test
- Set PE=1, test J=1,K̄=1 (shift in 1s)
- Test J=0,K̄=0 (shift in 0s)
- Test J=0,K̄=1 (hold mode - no change)
- Test J=1,K̄=0 (toggle mode)
- Mixed Operations Test
- Load pattern, then shift it out
- Shift pattern, then load new value
- Reset during shift operation
- Timing Violation Test
- Setup time violations (data changes too close to clock)
- Hold time violations (data changes too soon after clock)
- Pulse width violations (clock too narrow)
Sample Test Commands
>>: define file=hc74195_test
>>: get type=hc74195_test
# Test 1: Reset
>>: set mr=0
>>: simulate 50
>>: print q0,q1,q2,q3,q3_bar
# Expected: 0,0,0,0,1
# Test 2: Parallel load 1010
>>: set mr=1 pe=0 d0=1 d1=0 d2=1 d3=0
>>: clock cp period=100
>>: simulate 100
>>: print q0,q1,q2,q3
# Expected: 1,0,1,0
# Test 3: Shift in 1s
>>: set pe=1 j=1 k_bar=1
>>: clock cp period=100
>>: simulate 400
>>: print q0,q1,q2,q3
# Expected: 1,1,1,1
Validation Checklist
- ✓ Reset clears all outputs regardless of clock
- ✓ Parallel load captures D inputs on rising clock edge when PE=0
- ✓ Shift operations move data right by one position
- ✓ JK logic produces correct Q0 values for all four input combinations
- ✓ Q3_bar is always complement of Q3
- ✓ State is preserved correctly between evaluations
- ✓ Timing checks detect violations accurately
- ✓ X-propagation works correctly for unknown inputs
Lessons Learned
Key Implementation Patterns
1. Priority-Based Control Flow
Organizing the evaluation as a priority cascade (reset → parallel load → shift → hold) mirrors the datasheet description and makes the code easier to verify against specifications.
2. Separate State Variables
Using sq0-sq3 instead of just tracking outputs is essential for feedback logic.
The JK flip-flop needs to read its own previous value, which would create a circular dependency
if we used the output being computed.
3. Edge Detection Pattern
Saving the previous clock value and comparing it to the current value is the standard pattern for edge-triggered behavior. This works correctly even when the clock has unknown (X) values.
4. X-Propagation Discipline
Always handle unknown inputs explicitly. For the reset input, unknown (X) values must be treated conservatively - any flip-flop that's 0 stays 0, others become X.
Common Mistakes to Avoid
Always use inputs[i] & LEVEL_FIELD when extracting logic levels. The input bytes
contain both level (bits 0-1) and strength (bits 2-7) information. Comparing raw bytes will
give incorrect results.
The output array must exactly match the netlist O= declaration order.
For the 74HC195: Q0, Q1, Q2, Q3, Q3_bar, then state variables.
The output array must contain both outputs AND state variables. A common bug is forgetting to append state variables, which causes state to be lost between evaluations.
During JK hold mode, Q0 must read its own previous value. Using out[0]
(current output being computed) instead of sq0 (saved state) creates
incorrect behavior because out[0] hasn't been computed yet.
Performance Considerations
For this model, Python performance is acceptable. However, if you're simulating large arrays of shift registers (e.g., 1000 instances), consider:
- Converting to C for 10-100× speedup
- Using Simic's built-in shift register primitives if available
- Profiling to identify bottlenecks before optimizing
Complete Source Code
The full implementation with all timing checks and detailed comments is available at:
/home/gary/workspace/simic/tests/behavioral/python/HC74195.py
Supporting files:
- Netlist:
/home/gary/workspace/simic/tests/behavioral/python/tests/hc74195.net - Test script:
/home/gary/workspace/simic/tests/behavioral/python/tests/hc74195.run
The complete implementation is ~330 lines including comprehensive timing checks and documentation. Study this as a template for creating your own standard IC behavioral models.