Thursday, March 10, 2011

Remembering classic logic... (part 2)

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.

In this post we are going to look at D flip-flops... (which I think of as delay lines as well as single bit memories)

Suppose that one wanted to remember something, to cause a particular state (high or low) to persist after a signal changes... well, one way to achieve this is to feed the output of a circuit back into the same circuit as an input.  Lots of designs can be setup to function with "memory" like this, but a few became practical enough to have their own chips made.

The 74x74 is really just a set of 6 three input nand gates wired as two levels of logic with several feedback lines internally.  It takes four external inputs (set, clear, clock and data) and has two outputs Q and invQ.  If set is high and clear is low then the output Q will be high and invQ low. If set is low and clear is high then the output Q will be low and invQ high.  If both are high then both Q and invQ will be high (which I always thought odd since invQ should always be the opposite of Q, but in practice it makes some reset and failure detection designs easier this way).

The neat thing happens when both set and clear are low... Here the state of the outputs (Q and invQ) are dependent on the state of the data input only when the clock line is changing from low to high (positive edge).  In effect one can choose exactly what moment in time to sample the state of the data input and remember it for as long as one wished, or that output could be set to either low or high at any moment one chose.

The verilog hdl for this isn't hard, but there are a few points to make.  First I designed this to fit the specsheet behavior and not the actual implementation (or I would have used 6 nand gates wired like the original).  Second, the set and clear behavior is not synchronous with the clock.  In clocked designs I really like to have everything controlled bu the clock edges since it's easier to simulate and control timing and in general make things go faster with less metastability (when I do need to include async. control then I put it in a separate module).
assign AoutQ = (!Aset & !Aclr) ?  Aout : Aset;
   assign AinvQ = ( Aset &  Aclr) ?  1'b1 : ~AoutQ;
   assign BoutQ = (!Bset & !Bclr) ?  Bout : Bset;
   assign BinvQ = ( Bset &  Bclr) ?  1'b1 : ~BoutQ;
  
   // The data line is dependent on the clock to change the output. If we have a
   // control line signal then set the register instead for persistance.
   always @(posedge clock) begin
      if (Aset) begin
         Aout <= 1'b1; // set high always sets Aout                 
      end 
      else if (!Aset && Aclr) begin
         Aout <= 1'b0; // clr high only sets low if set is also low
      end
      else if (!Aset && !Aclr) begin
         Aout  <=  Adata; // if set and clr are low then remember the data signal     
      end     
   end // always @ (posedge clock)

   always @(posedge clock) begin
      if (Bset) begin
         Bout <= 1'b1; // set high always sets Aout                 
      end 
      else if (!Bset && Bclr) begin
         Bout <= 1'b0; // clr high only sets low if set is also low
      end
      else if (!Bset && !Bclr) begin
         Bout  <=  Bdata; // if set and clr are low then remember the data signal     
      end     
   end // always @ (posedge clock)

I think it's easier to understand why the circuit works the way it does this way (even though it will be a much larger implementation than is needed... but it's not technically correct...
Now just to make things interesting, I'll tell you that there is a bad mistake here (not obvious at first though...)  What happens here is that the only time the register is changed is when the clock edge occurs and not when the set or clear signals arrive... If the clock is fast enough it's probably not a problem, but there really isn't a good way to build this in programmable logic with registers.






This is the classic 6 nand gate with feedback solution...
   assign Anand1 = !(Anand4 & !Aclr  & clock);
   assign Anand2 = !(Adata  & !Aclr  & Anand3);
   assign Anand3 = !(clock  & Anand1 & Anand2);
   assign Anand4 = !(!Aset  & Anand2 & Anand1);
   assign AoutQ  = !(AinvQ  & Anand1 & !Aset);
   assign AinvQ  = !(AoutQ  & !Aclr  & Anand3);
 
   assign Bnand1 = !(Bnand4 & !Bclr  & clock);
   assign Bnand2 = !(Bdata  & !Bclr  & Bnand3);
   assign Bnand3 = !(clock  & Bnand1 & Bnand2);
   assign Bnand4 = !(!Bset  & Bnand2 & Bnand1);
   assign BoutQ  = !(BinvQ  & Bnand1 & !Bset);
   assign BinvQ  = !(BoutQ  & !Bclr  & Bnand3);
This maps out without clocks and therefore we can use as many level changes as we wish to change the state... but in reality it's a mess to hit timing when these types of designs get big.  Luckily we can often design things with fewer async. level triggers (even with the other classic 7400 series variants that don't use both set and clear).
Some interesting variants were also made.  The 74x173 has four D flip-flops but to fit them in the package the inverted (complementary) outputs were removed.  It adds two enable lines that allow the outputs to be tristated (which makes it easy to add to a multi-device bus).
 // Tristates are controlled by two enable lines (en0, en1).
   // Reset is dominant outside the clock
   assign AoutQ = (!en0 & !en1) ?  (Aout) : 1'bz;
   assign BoutQ = (!en0 & !en1) ?  (Bout) : 1'bz;
   assign CoutQ = (!en0 & !en1) ?  (Cout) : 1'bz;
   assign DoutQ = (!en0 & !en1) ?  (Dout) : 1'bz;
  
   // The data line is dependent on the clock to change the output. If we have a
   // control line signal then set the register instead for persistance.
   always @(posedge clock or posedge reset) begin
      if (reset) begin
     Aout <= 1'b0;
     Bout <= 1'b0;
     Cout <= 1'b0;
     Dout <= 1'b0;     
      end
      else if (!reset) begin
     Aout <= Adata;
     Bout <= Bdata;
     Cout <= Cdata;
     Dout <= Ddata;     
      end
   end // always @ (posedge clock)
 The 74x174 also leaves out the inverted outputs but instead of allowing tristate outputs it adds a single clear line to reset the outputs independent of the clock.  The changes in the hdl are straightforward (it is necessary to use the clear line in both the direct assigns as well as within the clock condition for consistent output).  The 74x273 is similar but has 8 D flip flops.


   assign AoutQ = clear ? Aout : 1'b0;
   assign BoutQ = clear ? Bout : 1'b0;
   assign CoutQ = clear ? Cout : 1'b0;
   assign DoutQ = clear ? Dout : 1'b0;
   assign EoutQ = clear ? Eout : 1'b0;
   assign FoutQ = clear ? Fout : 1'b0;
  
   // The data line is dependent on the clock to change the output but is overridden
   // by clear.
   always @(posedge clock) begin
      if (!clear) begin
         Aout <= 1'b0;
         Bout <= 1'b0;
         Cout <= 1'b0;
         Dout <= 1'b0;
         Eout <= 1'b0;
         Fout <= 1'b0; 
      end
      else if (clear) begin
         Aout <= Adata;
         Bout <= Bdata;
         Cout <= Cdata;
         Dout <= Ddata;        
         Eout <= Edata;        
         Fout <= Fdata;        
      end
   end // always @ (posedge clock)
 The 74x175 only has four D flip-flops, but includes the complimentary outputs (inverted).

  // Direct inversions
   assign AoutQ = clear ? Aout : 1'b0;
   assign BoutQ = clear ? Bout : 1'b0;
   assign CoutQ = clear ? Cout : 1'b0;
   assign DoutQ = clear ? Dout : 1'b0;
   assign AinvQ = ~AoutQ;
   assign BinvQ = ~BoutQ;
   assign CinvQ = ~CoutQ;
   assign DinvQ = ~DoutQ;
     
   // The data line is dependent on the clock to change the output but is overridden
   // by clear.
   always @(posedge clock or negedge clear) begin
      if (!clear) begin
         Aout <= 1'b0;
         Bout <= 1'b0;
         Cout <= 1'b0;
         Dout <= 1'b0; 
      end
      else if (clear) begin
         Aout <= Adata;
         Bout <= Bdata;
         Cout <= Cdata;
         Dout <= Ddata;       
      end
   end // always @ (posedge clock)
The 74x377 fits in 8 flip-flops and puts an enable on the clock but no way of setting or resetting the state other then through the data line.

   // The data line is dependent on the clock to change the output
   always @(posedge clock) begin
      if (!clockenable) begin
         AoutQ <= Adata;
         BoutQ <= Bdata;
         CoutQ <= Cdata;
         DoutQ <= Ddata;        
         EoutQ <= Edata;        
         FoutQ <= Fdata;         
         GoutQ <= Gdata;         
         HoutQ <= Hdata;
      end     
   end // always @ (posedge clock)
There are other variants but these are the ones I have in my box at the moment.  The important thing to take away here is that D flip-flops are a nice methods of retaining the state of a wire after the signal has left and that different methods of allowing that signal to be set or cleared, different ways of allowing the signal to be changed and different ways of allowing the signal to connect to the rest of the world (true/tristate) were important enough to many developers that independent chips were built with some of the variations.  Like the ubiquitous nand gate, D flip flops are one of those elements that can be combined in many ways to create new things (like shift registers - just feed serially connected outputs for one to the next in a chain). 

It's interesting to note that most of the way I would design things now for performance would not have asynchronous set/reset/clear/enable/tristate signals and would rather have them under the control of a system clock edge transition.  I'm not certain that at the time these components were thought out that metastability issues were as significant (or perhaps were less important until the overall complexity of systems increased).