@@ -63,10 +63,15 @@ interpret reg -> bv[3]
6363type opc
6464interpret opc -> bv[3]
6565
66- # Cache index / hi_addr field type (4 bits): a line is at index bfe[0][3](A)
67- # and stores hi_addr = bfe[4][7](A).
66+ # Cache line and its field types. A line is a bv[22]:
67+ # [21] full [20] dirty [19:16] hi_addr [15:0] data
68+ # Address A maps to line index bfe[0][3](A); the hi_addr field is bfe[4][7](A).
69+ type cline
70+ interpret cline -> bv[22]
6871type nib
6972interpret nib -> bv[4]
73+ type bit
74+ interpret bit -> bv[1]
7075
7176# The clock: one exported action driven by the environment.
7277
@@ -484,14 +489,11 @@ object cpu = {
484489 # (1) a dirty cache line's address is dirty in the reference;
485490 # (2) a present cache line holds the reference's MEM-stage value;
486491 # (4) a not-dirty address holds the reference value in main memory.
487- # ((3), the I-cache, arrives in M3 .)
492+ # ((3), the I-cache, is below .)
488493 #
489- # Direct-mapped structural invariant: a valid line at index I caches an
490- # address whose index is I (maintained by every install, since we install
491- # at m_index = bfe[0][3](m_addr)). Without this the solver can place an
492- # address at the wrong line and write a stale victim back to it.
493- invariant dcf(I) -> bfe[0][3](dct(I)) = I
494- invariant icf(I) -> bfe[0][3](ict(I)) = I
494+ # (With a packed line the tag holds only hi_addr, so the address cached at
495+ # index I is structurally concat(hi_addr, I), whose index is I -- no
496+ # separate "line is at the right index" invariant is needed.)
495497 invariant ~trace.st(trace.now).error -> (dc_dirty(A) -> trace.st(mcommit).ddirty(A))
496498 invariant ~trace.st(trace.now).error -> (dc_present(A) -> dc_val(A) = trace.st(mcommit).mem(A))
497499 invariant ~trace.st(trace.now).error -> (~dc_dirty(A) -> mem(A) = trace.st(mcommit).mem(A))
@@ -593,34 +595,32 @@ object cpu = {
593595 var mem(A:addr) : word # unified main memory (instructions and data)
594596
595597 # ---- D-cache (direct-mapped, write-back, 16 lines x 1 word) ----
596- # The specified line format is a 22-bit vector
597- # [21] full [20] dirty [19:16] hi_addr [15:0] data
598- # We store the fields as parallel arrays sharing the index bfe[0][3](A); this
599- # is the same direct-mapped cache (in RTL, parallel narrow memories with a
600- # shared index) but avoids bit-packing plumbing. hi_addr = bfe[4][7](A).
601- # Data accesses (LD/ST) go through this cache; instruction fetch in M2 still
602- # reads main memory directly (the I-cache arrives in M3), so a store that is
603- # only in the D-cache leaves a stale instruction in main memory -- which is
604- # exactly the incoherence the ISA's ddirty/error mechanism accounts for.
605-
606- var dcf(I:nib) : bool # full (valid)
607- var dcd(I:nib) : bool # dirty
608- var dct(I:nib) : addr # tag: the full address cached in this line
609- # (its low nibble is always the line index I;
610- # storing the full address rather than just the
611- # hi_addr nibble avoids a concat and is the same
612- # direct-mapped cache)
613- var dcw(I:nib) : word # data word
598+ # Each line is a packed bv[22] as described at the top: [21] full, [20] dirty,
599+ # [19:16] hi_addr, [15:0] data. Lines are built with `concat` and their fields
600+ # read with `bfe` (see the ln_* accessors below). Data accesses (LD/ST) go
601+ # through this cache; instruction fetch reads the separate I-cache. A store
602+ # that lives only in the (write-back) D-cache leaves a stale value in main
603+ # memory -- the incoherence the ISA's ddirty/error mechanism accounts for.
604+
605+ var dcache(I:nib) : cline
614606
615607 # ---- I-cache (direct-mapped, read-only, 16 lines x 1 word) ----
616608 # Instruction fetch reads this cache; it is filled from main memory on a miss
617- # and never written by the CPU (so no dirty bit). FLUSH invalidates a line
618- # (M3b). Being filled from possibly-stale main memory, it can hold a stale
609+ # and never written (so its dirty bit is always 0 ). FLUSH invalidates a line.
610+ # Being filled from possibly-stale main memory, it can hold a stale
619611 # instruction; the ISA's ddirty/error accounts for that.
620612
621- var icf(I:nib) : bool # full (valid)
622- var ict(I:nib) : addr # tag: the full address cached in this line
623- var icw(I:nib) : word # cached instruction word
613+ var icache(I:nib) : cline
614+
615+ # ---- packed-line field accessors (used by both datapath and invariants) ----
616+ function ln_full(L:cline) : bool
617+ definition ln_full(L) = (bfe[21][21](L):bit) = 1
618+ function ln_dirty(L:cline) : bool
619+ definition ln_dirty(L) = (bfe[20][20](L):bit) = 1
620+ function ln_hi(L:cline) : nib
621+ definition ln_hi(L) = bfe[16][19](L)
622+ function ln_data(L:cline) : word
623+ definition ln_data(L) = bfe[0][15](L)
624624
625625 # ---- main-memory read-fill controller (multi-cycle) ----
626626 # Read fills take two cycles: an initiating cycle (memory free) and a busy
@@ -722,10 +722,12 @@ object cpu = {
722722
723723 wire f_index : nib
724724 definition f_index = bfe[0][3](pc)
725+ wire f_iline : cline # the I-cache line at the fetch index
726+ definition f_iline = icache(f_index)
725727 wire f_ihit : bool
726- definition f_ihit = icf(f_index ) & (ict(f_index ) = pc )
728+ definition f_ihit = ln_full(f_iline ) & (ln_hi(f_iline ) = bfe[4][7](pc) )
727729 wire fetched : word
728- definition fetched = (icw(f_index ) if f_ihit else mem(pc))
730+ definition fetched = (ln_data(f_iline ) if f_ihit else mem(pc))
729731
730732 wire f_is_branch : bool
731733 definition f_is_branch = ((bfe[13][15](fetched):opc) = 6)
@@ -754,20 +756,26 @@ object cpu = {
754756
755757 wire m_index : nib
756758 definition m_index = bfe[0][3](m_addr)
759+ wire m_hi : nib # hi_addr of the accessed address
760+ definition m_hi = bfe[4][7](m_addr)
761+ wire m_line : cline # the D-cache line at this index
762+ definition m_line = dcache(m_index)
757763 wire m_line_full : bool
758- definition m_line_full = dcf(m_index )
764+ definition m_line_full = ln_full(m_line )
759765 wire m_line_dirty : bool
760- definition m_line_dirty = dcd(m_index )
761- wire m_line_addr : addr # full address currently cached in this line
762- definition m_line_addr = dct(m_index )
766+ definition m_line_dirty = ln_dirty(m_line )
767+ wire m_line_hi : nib # hi_addr currently cached in this line
768+ definition m_line_hi = ln_hi(m_line )
763769 wire m_line_data : word
764- definition m_line_data = dcw(m_index )
770+ definition m_line_data = ln_data(m_line )
765771 wire m_hit : bool
766- definition m_hit = m_line_full & (m_line_addr = m_addr)
772+ definition m_hit = m_line_full & (m_line_hi = m_hi)
773+ wire m_iline : cline # the I-cache line at this index
774+ definition m_iline = icache(m_index)
767775 wire m_ihit : bool # I-cache holds the (flush) address m_addr
768- definition m_ihit = icf(m_index ) & (ict(m_index ) = m_addr )
776+ definition m_ihit = ln_full(m_iline ) & (ln_hi(m_iline ) = m_hi )
769777 wire m_victim_addr : addr # address to write the victim back to
770- definition m_victim_addr = m_line_addr
778+ definition m_victim_addr = concat(m_line_hi, m_index)
771779 wire m_fill_data : word # main-memory word for m_addr (pre-clock)
772780 definition m_fill_data = mem(m_addr)
773781
@@ -782,19 +790,27 @@ object cpu = {
782790 wire dmem_stall : bool
783791 definition dmem_stall = d_miss & ~d_fill_go
784792
785- # ---- D-cache query helpers (used by the invariants, for any address A) ----
793+ # ---- cache query helpers (used by the invariants, for any address A) ----
794+ # A is present in a cache iff the line at its index is full and its hi_addr
795+ # field matches A's hi_addr; the cached address is then structurally
796+ # concat(hi_addr, index(A)) = A, so no separate "right index" invariant is
797+ # needed (unlike a full-address tag).
786798
799+ function dc_line(A:addr) : cline
800+ definition dc_line(A) = dcache(bfe[0][3](A))
787801 function dc_present(A:addr) : bool
788- definition dc_present(A) = dcf(bfe[0][3] (A)) & (dct(bfe[0][3] (A)) = A )
802+ definition dc_present(A) = ln_full(dc_line (A)) & (ln_hi(dc_line (A)) = bfe[4][7](A) )
789803 function dc_dirty(A:addr) : bool
790- definition dc_dirty(A) = dc_present(A) & dcd(bfe[0][3] (A))
804+ definition dc_dirty(A) = dc_present(A) & ln_dirty(dc_line (A))
791805 function dc_val(A:addr) : word
792- definition dc_val(A) = dcw(bfe[0][3] (A))
806+ definition dc_val(A) = ln_data(dc_line (A))
793807
808+ function ic_line(A:addr) : cline
809+ definition ic_line(A) = icache(bfe[0][3](A))
794810 function ic_present(A:addr) : bool
795- definition ic_present(A) = icf(bfe[0][3] (A)) & (ict(bfe[0][3] (A)) = A )
811+ definition ic_present(A) = ln_full(ic_line (A)) & (ln_hi(ic_line (A)) = bfe[4][7](A) )
796812 function ic_val(A:addr) : word
797- definition ic_val(A) = icw(bfe[0][3] (A))
813+ definition ic_val(A) = ln_data(ic_line (A))
798814
799815 # ---- reset ----
800816
@@ -806,8 +822,8 @@ object cpu = {
806822 w_valid := false;
807823 rf(R) := 0;
808824 mem(A) := init_mem(A);
809- dcf (I) := false ; # all D-cache lines empty
810- icf (I) := false; # all I-cache lines empty
825+ dcache (I) := 0 ; # all D-cache lines empty (full bit = 0)
826+ icache (I) := 0; # all I-cache lines empty
811827 mbusy := false; # no memory fill in progress
812828 }
813829
@@ -836,29 +852,24 @@ object cpu = {
836852 mem(m_victim_addr) := m_line_data
837853 };
838854 if m_opcode = 5 {
839- # ST: install a dirty line holding the stored word. A 1-word line
840- # is fully overwritten, so a write miss needs no fetch.
841- dcf(m_index) := true;
842- dcd(m_index) := true;
843- dct(m_index) := m_addr;
844- dcw(m_index) := m_store
855+ # ST: install a dirty line holding the stored word (full, dirty,
856+ # hi_addr, data). A 1-word line is fully overwritten, so a write
857+ # miss needs no fetch.
858+ dcache(m_index) := (concat(1:bit, 1:bit, m_hi, m_store) : cline)
845859 } else if ~m_hit {
846860 # LD miss: install a clean line with the fetched word.
847- dcf(m_index) := true;
848- dcd(m_index) := false;
849- dct(m_index) := m_addr;
850- dcw(m_index) := m_fill_data
861+ dcache(m_index) := (concat(1:bit, 0:bit, m_hi, m_fill_data) : cline)
851862 }
852863 };
853864 # FLUSH m_addr: write the D-cache line back if it is present and dirty,
854- # then evict m_addr from both caches, so a later fetch of m_addr misses
855- # and refills from the (now up-to-date) main memory.
865+ # then evict m_addr from both caches (full bit -> 0) , so a later fetch of
866+ # m_addr misses and refills from the (now up-to-date) main memory.
856867 if ~dmem_stall & m_valid & (m_opcode = 7) {
857868 if m_hit & m_line_dirty {
858869 mem(m_addr) := m_line_data
859870 };
860- if m_hit { dcf (m_index) := false };
861- if m_ihit { icf (m_index) := false }
871+ if m_hit { dcache (m_index) := 0 };
872+ if m_ihit { icache (m_index) := 0 }
862873 };
863874
864875 # EX: held while MEM stalls for a D-fill; bubbled on a data hazard;
@@ -903,9 +914,8 @@ object cpu = {
903914 d_pred := f_ptaken;
904915 d_pc := pc;
905916 if ~f_ihit {
906- icf(f_index) := true;
907- ict(f_index) := pc;
908- icw(f_index) := fetched
917+ # install a clean I-cache line (dirty bit unused = 0)
918+ icache(f_index) := (concat(1:bit, 0:bit, (bfe[4][7](pc):nib), fetched) : cline)
909919 };
910920 pc := pred_next_pc
911921 }
0 commit comments