Wednesday, March 23, 2011

Remembering classic logic... (part 9)

Recently I pulled out my boxes of 7400 series logic (working on a few old school projects that were idled for a bit).  There's something special about these chips...

So, on a whim I thought that I'd bang out verilog modules for the chips I have on hand.  I'm putting the files for these modules in the tanukifu project here.

There are a lot of times one wants to step through a sequence of events, cause a certain number of events to occur or wait until a certain number of events have occurred before doing something... for these things counters are nice to have available.  If you chain flip-flops/registers together so that the output from one triggers the input clock on the next in line we can count easily in base two. 

The 74x90 is a BCD counter - oftentimes we need to convert to base 10 (because people commonly use base 10) and one way to do that is to combine a counter that goes to 5 and a second counter that goes to 2 - depending on how it's wired up we can have a 1:2 counter/divider and a 1:5 counter/divider or a 1:10 counter/divider.

The hdl in verilog for this can be done in a number of ways, but I'm doing it here in a way where it's easy to see how one state changes to the next (but this isn't an efficient way)...


   // the div2 section is tied to the Q0 output Aset goes high if the async set/reset triggers occur
   // but otherwise toggle based on the clk0.
   always @ (negedge clk0 or posedge Aset) begin
      if (Aset) begin
         // if preset or clear states indicate we should set intent
         Qint0 <= Q0;
      end
      else if (!Aset) begin
     // toggle state
         Qint0 <= ~Qint0; 
      end
   end

   // the div5 section is tied to clk1
   always @ (negedge clk1 or posedge Aset) begin
      if (Aset) begin
         // if preset or clear states indicate we should set intent
         Qint1 <= Q1;
         Qint2 <= Q2;
         Qint3 <= Q3;
      end
      else if (!Aset) begin
     // increase count to 5 then cycle over
     if (!Qint3 & !Qint2 & !Qint1) begin
        Qint1 <= 1'b1;
        Qint2 <= 1'b0;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 & !Qint2 &  Qint1) begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b1;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 &  Qint2 & !Qint1) begin
        Qint1 <= 1'b1;
        Qint2 <= 1'b1;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 &  Qint2 &  Qint1) begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b0;
        Qint3 <= 1'b1;       
     end
     else begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b0;
        Qint3 <= 1'b0;
     end
      end
   end
  
   // async. set and clear logic, Aset is used to pass the async signals back to the output register
   // state determination.
   always @ (*) begin  
      if (!(reset0 & reset1) & (set0 & set1)) begin
         // set output high (1001)
         Q0   <= 1'b1;
         Q1   <= 1'b0;
         Q2   <= 1'b0;
         Q3   <= 1'b1;
         Aset <= 1'b1;
      end
      else if (reset0 & reset1) begin  
         // clear output
         Q0   <= 1'b0;
         Q1   <= 1'b0;
         Q2   <= 1'b0;
         Q3   <= 1'b0;
         Aset <= 1'b1;
      end
      else begin
         // change state
         Q0    <= Qint0;
         Q1    <= Qint1;
         Q2    <= Qint2;
         Q3    <= Qint3;
         Aset  <= 1'b0;
      end
   end // always @ (*)
If instead of base 10 one wanted just a 4 bit counter it's easier to describe in verilog because we can imply the connections between the registers. 

This is a 74x161... it's actually a bit more complicated than the 74x90 because it allow parallel loading of the registers and includes a terminal count output that makes it easier to chain multiple counters together... The 74x160 and 74x162 are BCD counters and the 74x161 and 74x163 are binary counters.  The 74x160 and 74x161 have async. resets and the 74x162 and 74x163 have clock sync. resets.

   // assign for terminal count output
   assign TC = Q & 4'b1111; // state 1111
 
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         Q <= 4'b0000;
      end
      else if (reset) begin
         if (!PE) begin
            // parallel load
            Q <= P;       
         end
         else if (PE & CET & CEB) begin
            Q <= Q + 1'b1;        
         end           
      end
   end 
  
 The 74x590 is one of my more often used chips in the drawer because it increases the counter size to 8 bits and adds the ability to tristate the outputs.  It also has a separate register clock so the internal counter can be changed without making those changes visible on the outputs until a separate clock signal occurs.


   // tristate the outputs
   assign Q = (G) ? 8'bzzzzzzzz : Qreg;

   // RCO is the saturation flag
   assign RCO = Qreg & 8'b11111111;
     
   // copy the internal counts to the registers on RCK
   always @ (posedge RCK) begin
      Qreg <= Qint;     
   end
  
   // reset applies to the internal counters.
   // increment on CCK
   always @ (posedge CCK or negedge reset) begin
      if (!reset) begin
         Qint <= 8'b00000000;
      end
      else if (reset & CCKEN) begin
         Qint <= Qint + 1;
      end 
   end  
While there are lots of variants for the counter chips,  I will mention here the 74x569 which adds the ability to count both up or down.  like the other variants there is a bcd version (74x568) but it's easier to understand the binary counters.  It also includes both sync. and async. resets... yay...  This is one of my favorites...


   // assign for terminal count output
   assign TC = Qreg & 4'b1111; // state 1111

   // tristates for output enable (OE)
   assign Q = (OE) ? 4'bz : Qreg;  

   // CC clock propagation
   assign CC = (SR & PE & !CET & !CEP & !TC) ? clock : 1'b1;

   // up/down counter - sync and async reset, 2 enables, direction
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         // async. reset
         Qreg <= 4'b0000;  
      end
      else if (reset) begin
         if (!SR) begin
            // sync. reset
            Qreg <= 4'b0000;           
         end
         else if (SR & !PE) begin
            // parallel load
            Qreg <= D;     
         end
         else if (SR & !CEP & !CET & UD) begin
            Qreg <= Qreg + 1;      
         end
         else if (SR & !CEP & !CET & !UD) begin
            Qreg <= Qreg - 1;      
         end
      end
   end  
I'd also like to mention the 74x4520... this chip contains two 4 bits counters and while it doesn't have parallel load or up/down directional control or easy chaining signals it has the ability to use either the positive edge of the clock or the negative edge of the enable line to increment the counter. 

The hdl is easy in verilog...

assign tripA = !(!clkA & enA);
   assign tripB = !(!clkB & enB);
        
   always @ (posedge tripA or posedge resetA) begin
      if (resetA) begin
         // async. reset
         QA <= 4'b0000;  
      end
      else if (!resetA) begin
         QA <= QA + 1;  
      end
   end
  
   always @ (posedge tripB or posedge resetB) begin
      if (resetB) begin
         // async. reset
         QB <= 4'b0000;  
      end
      else if (!resetB) begin
         QB <= QB + 1;  
      end
   end  
  
Just to give you an idea of how nice it is to have verilog do the connections between the registers when they are grouped (so they behave like a multidigit number), let's look at the wrong way to code a 74x568 (just like the 74x569 above but in bcd instead of binary).  (This isn't really the "wrong way" but it's an inefficient way - logically treating the 1:2 and 1:5 counters separately and within the 1:5 treating the registers separately and having explicit states for the up as well as the down movements)... perhaps it would be better to say the "ugly and difficult to maintain and understand" way rather than the "wrong way"...

 // assign for terminal count output
   assign RCO = Q0 & !Q1 & !Q2 & Q3; // state 1001

   // tristates for output enable (OE)
   assign Q = (OE) ? 4'bz : {Q3, Q2, Q1, Q0};  


   // Just for fun I'm doing every state with up/down as an input
   // rather than the clean way... I'll use the clean way with the
   // binary counter version... I'm doing this to show how increasing the
   // abilities of a module can become unmanageable... You should quickly
   // realize that setting up a simple state machine with external control logic
   // would be more efficient...
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         Q0 = 1'b0;
         Q1 = 1'b0;
         Q2 = 1'b0;
         Q3 = 1'b0;     
      end
      else if (reset) begin
         if (!SR) begin
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end
         else if (!load) begin
            // parallel load
            Q0 = A;
            Q1 = B;
            Q2 = C;
            Q3 = D;       
         end
         else if (load & !ENP & !ENT & !Q0 & !Q1 & !Q2 & !Q3 & UD) begin
            // state 0000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end    
         else if (load & !ENP & !ENT &  Q0 & !Q1 & !Q2 & !Q3 & UD) begin
            // state 1000
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT & !Q0 &  Q1 & !Q2 & !Q3 & UD) begin
            // state 0100
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  Q1 & !Q2 & !Q3 & UD) begin
            // state 1100
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 &  Q2 & !Q3 & UD) begin
            // state 0010
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  !Q1 &  Q2 & !Q3 & UD) begin
            // state 1010
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  Q1 &  Q2 & !Q3 & UD) begin
            // state 0110
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &   Q1 &  Q2 & !Q3 & UD) begin
            // state 1110
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 & !Q2 &  Q3 & UD) begin
            // state 1000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end            
         else if (load & !ENP & !ENT &   Q0 &  !Q1 & !Q2 &  Q3 & UD) begin
            // state 1001
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end
         else if (load & !ENP & !ENT & !Q0 & !Q1 & !Q2 & !Q3 & !UD) begin
            // state 1001
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end    
         else if (load & !ENP & !ENT &  Q0 & !Q1 & !Q2 & !Q3 & !UD) begin
            // state 0000
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT & !Q0 &  Q1 & !Q2 & !Q3 & !UD) begin
            // state 1000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  Q1 & !Q2 & !Q3 & !UD) begin
            // state 0100
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 &  Q2 & !Q3 & !UD) begin
            // state 1100
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  !Q1 &  Q2 & !Q3 & !UD) begin
            // state 0010
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  Q1 &  Q2 & !Q3 & !UD) begin
            // state 1010
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &   Q1 &  Q2 & !Q3 & !UD) begin
            // state 0110
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 & !Q2 &  Q3 & !UD) begin
            // state 1110
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT &   Q0 &  !Q1 & !Q2 &  Q3 & !UD) begin
            // state 0001
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end
      end
   end  
If you look back at the 74x569, imagine that you just made a 1 bit and a 3 bit counter (trapped at 4) instead of the 4 bit binary counter.  It would take very little additional logic to connect overflow/underflow (add to the 1 bit pass overflow to the clock for the 3 bit one to add - and - subtract from the 3 bit and pass the underflow to the 1 bit to subtract) and you would have a cute little easy to understand and maintain hdl... In general if you have lots of things involved in state changes withing a block, or lots of states withing a block... there is an easier way to design it.  The important thing to remember is that there isn't a right or wrong way, there are always tradeoffs in size and speed and maintainability as well as style.

Counters are extremely useful, but I find that many times the design of the rest of the circuit gets adjusted to the counter that you want to use... while there are a number of variants with loading and triggering and signal passing capabilities, there isn't a great way to build a really universal module (which is why there are so many variations produced).  If you are designing the hardware it's relatively easy to build the exact counter you need - but when you have to start adding more and more control logic before the counter you have available, it's easy to have timing problems at higher speeds.