Hi,

thanks for the first reply to any of my reported issues!

Klaus Rudolph:
> I only have taken a very short look over your mail. But it sounds not
> very good to me because replacing a clear templated structure with: void
> pointers, c macros, casting, exclude stuff for swig, etc

- I'd consider "clear templated structure" debatable. IMHO, it's a
confusing cyclic structure, for example, having IOReg<HWPort> pin_reg as
a member of HWPort.
- Also, instantiating a template class for every Hardware/peripheral
just because of a callback seems wrong to me. It makes it impossible to
cast the register to sth else without knowing the template parameter/HW
type (see below).
- void*: I also don't like it, but the IOReg is given "this" as its
"owner" and the proper callback handlers in the constructor. The static
callback handlers are /private/ class methods, only visible from within
the same class. I don't see many opportunities to mix the types of
"this" and the callbacks.
- c macros: they are an easy way to generate the g/setters. If there's
another way, I'm open to it. Recent GCC (e.g. Debian stable: 4.9.2)
generates nice warnings/errors and also cites the macro which was
expanded and resulted in the error. I also did not want to rewrite all
the object s/getter methods, thus, only wrapping them in a static class
method. Using the macro for standard unsigned char getter/setters, IMHO,
keeps the code quite readable.
- exclude stuff for swig: the callback handlers aren't needed for swig,
not intended to be ever called from swig, nor does swig like void*.
Naturally then, these methods should be excluded from swig.


> Sorry that I have very very less time to dig into your proposal, but
> only from the given excerpt here in the mail I tend to NOT change the
> existing code.

well, the motivation to change IOReg is actually a bit more complex:
Current IOReg<P> does only provide set and get methods (via
RWMemoryMember). The bitwise SBI/CBI instructions are implemented as
read/modify/write in AvrDevice, which leads to a severe bug in
simulation: the lower 0x20 IO registers are bitwise accessible and RMW
is just wrong.

Example:
SBI of bit 0 in PINB register should toggle (xor) PORTB.PB0. Using RMW
approach, we read PINB = 0xff (because DDRB is input/tristate, read as
'1' for each pin), we OR the bitmask (1<<0)=1 on top of it (=0xff) and
write it back to PINB, *which toggles all PORTB bits* --> bug.

To fix it, I wanted to add a set_bit() method to IOReg<P>. In AvrDevice,
I then cast RWMemoryMember to IOReg:

> bool AvrDevice::SetIORegBit(unsigned addr, unsigned bitaddr, bool bval) {
>     IOReg<XXX> *reg = dynamic_cast<IOReg<XXX>*>(rw[addr + registerSpaceSize]);
>     reg->set_bit(bitaddr, bval);
>     return true;
> }

But I cannot do this because there's no way to deduce the template
parameter "XXX". So, template is bad... removing the template is a solution.

For completeness, here's attached the patch to fix the actual bug, after
the template is removed/assuming previous patch is applied.

- it introduces a new class "BitAccessibleRegister" with a default
  implementation using RMW for set_bit()
- makes registers in the IO memory space children of
  BitAccessibleRegister (e.g. GPIORegister, CPKPRRegister)
  for new hierarchy, see attached PNG.
- it fixes PORTx and DDRx registers not being traces correctly
- fixes PINx write to toggle PORTx using single-bit SBI/CBI instructions

Cheers,
panic
>From 11f5325bf38d8bf9c88c1c37af0928f23f4eaa56 Mon Sep 17 00:00:00 2001
From: panic <li...@xandea.de>
Date: Mon, 12 Jun 2017 23:34:33 +0200
Subject: [PATCH] fix single-bit access to PINx and I/O registers

---
 src/avrdevice.cpp  |   8 +--
 src/hwport.cpp     |  15 ++++-
 src/hwport.h       |   3 +
 src/hwsreg.h       |   4 +-
 src/rwmem.cpp      |  27 ++++++--
 src/rwmem.h        | 187 +++++++++++++++++++++++++++++------------------------
 src/specialmem.cpp |   8 +--
 src/specialmem.h   |   8 +--
 8 files changed, 154 insertions(+), 106 deletions(-)

diff --git a/src/avrdevice.cpp b/src/avrdevice.cpp
index 51098cb..11ea1f3 100644
--- a/src/avrdevice.cpp
+++ b/src/avrdevice.cpp
@@ -473,12 +473,8 @@ bool AvrDevice::SetIOReg(unsigned addr, unsigned char val) {
 
 bool AvrDevice::SetIORegBit(unsigned addr, unsigned bitaddr, bool bval) {
     assert(addr < 0x20);  // only first 32 IO registers are bit-settable
-    unsigned char val = *(rw[addr + registerSpaceSize]);
-    if(bval)
-      val |= 1 << bitaddr;
-    else
-      val &= ~(1 << bitaddr);
-    *(rw[addr + registerSpaceSize]) = val;
+    BitAccessibleRegister *reg = dynamic_cast<BitAccessibleRegister*>(rw[addr + registerSpaceSize]);
+    reg->set_bit(bitaddr, bval);
     return true;
 }
 
diff --git a/src/hwport.cpp b/src/hwport.cpp
index 77c3a16..2d430f6 100644
--- a/src/hwport.cpp
+++ b/src/hwport.cpp
@@ -40,7 +40,7 @@ HWPort::HWPort(AvrDevice *core, const string &name, bool portToggle, int size):
     port_reg(this, "PORT",
              this, &HWPort::GetPort, &HWPort::SetPort),
     pin_reg(this, "PIN",
-            this, &HWPort::GetPin, &HWPort::SetPin),
+            this, &HWPort::GetPin, &HWPort::SetPin, &HWPort::SetPinBit),
     ddr_reg(this, "DDR",
             this, &HWPort::GetDdr, &HWPort::SetDdr)
 {
@@ -103,6 +103,9 @@ void HWPort::CalcOutputs(void) { // Calculate the new output value to be transmi
         pintrace[actualBitNo]->change(p[actualBitNo].outState);
     }
     pin = tmpPin;
+
+    port_reg.hardwareChange(port);
+    ddr_reg.hardwareChange(ddr);
     pin_reg.hardwareChange(pin);
 }
 
@@ -124,4 +127,14 @@ void HWPort::SetPin(unsigned char val) {
         avr_warning("Writing of 'PORT%s.PIN' (with %d) is not supported.", myName.c_str(), val);
 }
 
+void HWPort::SetPinBit(unsigned char bitpos, bool val) {
+    unsigned char v = (val ? 1 : 0) << bitpos;
+    if (portToggleFeature) {
+        port ^= v;
+        CalcOutputs();
+        port_reg.hardwareChange(port);
+    } else
+        avr_warning("Writing of 'PORT%s.PIN' (with %d) is not supported.", myName.c_str(), v);
+}
+
 /* EOF */
diff --git a/src/hwport.h b/src/hwport.h
index 42e6acd..571553f 100644
--- a/src/hwport.h
+++ b/src/hwport.h
@@ -60,6 +60,9 @@ class HWPort: public Hardware, public TraceValueRegister {
         IOREG_SETGET_UCHAR(HWPort, Port)
         IOREG_SETGET_UCHAR(HWPort, Ddr)
         IOREG_SETGET_UCHAR(HWPort, Pin)
+        static void SetPinBit(void *this_ptr, unsigned char bitpos, bool bval) {
+            static_cast<HWPort*>(this_ptr)->SetPinBit(bitpos, bval);
+        }
 #endif
 
     public:
diff --git a/src/hwsreg.h b/src/hwsreg.h
index c1975e7..e76b725 100644
--- a/src/hwsreg.h
+++ b/src/hwsreg.h
@@ -62,10 +62,10 @@ class HWSreg: public HWSreg_bool {
   \todo Replace the status register with an ordinary byte somewhere and simple
   inline access functions sN(), gN() to get/set flags. This should also make
   accesses faster. */
-class RWSreg: public RWMemoryMember {
+class RWSreg: public BitAccessibleRegister {
     
     public:
-        RWSreg(TraceValueRegister *registry, HWSreg *s): RWMemoryMember(registry, "SREG"), status(s) {}
+        RWSreg(TraceValueRegister *registry, HWSreg *s): BitAccessibleRegister(registry, "SREG"), status(s) {}
         //! reflect a change, which comes from CPU core
         void trigger_change(void) { tv->change((int)*status); }
 
diff --git a/src/rwmem.cpp b/src/rwmem.cpp
index 8309af3..e174f3e 100644
--- a/src/rwmem.cpp
+++ b/src/rwmem.cpp
@@ -91,9 +91,24 @@ RWMemoryMember::~RWMemoryMember() {
         delete tv;
 }
 
+BitAccessibleRegister::BitAccessibleRegister()
+    : RWMemoryMember() {}
+
+BitAccessibleRegister::BitAccessibleRegister(
+    TraceValueRegister *registry,
+    const std::string &tracename)
+    : RWMemoryMember(registry, tracename) {}
+
+void BitAccessibleRegister::set_bit(unsigned char bitpos, bool bval) {
+    if (bval)
+        set(get() | (1<<bitpos));
+    else
+        set(get() & ~(1<<bitpos));
+}
+
 CLKPRRegister::CLKPRRegister(AvrDevice *core,
                              TraceValueRegister *registry):
-        RWMemoryMember(registry, "CLKPR"),
+        BitAccessibleRegister(registry, "CLKPR"),
         Hardware(core),
         _core(core) {
     if(_core->fuses->GetFuseBit(AvrFuses::FB_CKDIV8))
@@ -142,7 +157,7 @@ void CLKPRRegister::set(unsigned char v) {
 
 XDIVRegister::XDIVRegister(AvrDevice *core,
                              TraceValueRegister *registry):
-        RWMemoryMember(registry, "XDIV"), 
+        BitAccessibleRegister(registry, "XDIV"), 
         Hardware(core)
 {
     Reset();
@@ -168,7 +183,7 @@ void XDIVRegister::set(unsigned char v) {
 OSCCALRegister::OSCCALRegister(AvrDevice *core,
                              TraceValueRegister *registry,
                              int cal):
-        RWMemoryMember(registry, "OSCCAL"),
+        BitAccessibleRegister(registry, "OSCCAL"),
         Hardware(core),
         cal_type(cal)
 {
@@ -211,7 +226,7 @@ unsigned char RAM::get() const { return value; }
 void RAM::set(unsigned char v) { value=v; }
 
 InvalidMem::InvalidMem(AvrDevice* _c, int _a):
-    RWMemoryMember(),
+    BitAccessibleRegister(),
     core(_c),
     addr(_a) {}
 
@@ -232,7 +247,7 @@ void InvalidMem::set(unsigned char c) {
 }
 
 NotSimulatedRegister::NotSimulatedRegister(const char * message_on_access_)
-    : message_on_access(message_on_access_)  {}
+    : BitAccessibleRegister(), message_on_access(message_on_access_)  {}
 
 unsigned char NotSimulatedRegister::get() const {
     avr_warning("%s (read from register)", message_on_access);
@@ -244,7 +259,7 @@ void NotSimulatedRegister::set(unsigned char c) {
 }
 
 IOSpecialReg::IOSpecialReg(TraceValueRegister *registry, const std::string &name):
-    RWMemoryMember(registry, name)
+    BitAccessibleRegister(registry, name)
 {
     Reset();
 }
diff --git a/src/rwmem.h b/src/rwmem.h
index 3168d2b..3d194ee 100644
--- a/src/rwmem.h
+++ b/src/rwmem.h
@@ -87,15 +87,111 @@ class RWMemoryMember {
         const bool isInvalid;
 };
 
+//! I/O Registers within the I/O address range from 0x00 to 0x1F are directly
+//! bit accessible via the SBI and CBI instructions.
+class BitAccessibleRegister : public RWMemoryMember {
+    public:
+        explicit BitAccessibleRegister();
+        explicit BitAccessibleRegister(
+            TraceValueRegister *registry,
+            const std::string &tracename);
+
+        //! set or clear a single bit in a register.
+        //! By default, this emulates a full read/modify/write cycle.
+        virtual void set_bit(unsigned char bitpos, bool bval);
+        // TODO maybe we can also provide default implementations for get()
+        //      and set() here, instead of duplicating them every time
+};
+
+//! helper for IOReg static callback wrappers
+#define IOREG_SET_UCHAR(cls, reg)                           \
+    static void Set##reg(void *this_ptr, unsigned char val) \
+    { static_cast<cls*>(this_ptr)->Set##reg(val); }
+
+#define IOREG_GET_UCHAR(cls, reg)                           \
+    static unsigned char Get##reg(void *this_ptr)           \
+    { return static_cast<cls*>(this_ptr)->Get##reg(); }
+
+#define IOREG_SETGET_UCHAR(cls, reg)                        \
+    IOREG_SET_UCHAR(cls, reg)                               \
+    IOREG_GET_UCHAR(cls, reg)
+
+//! IO register to be specialized for a certain class/hardware
+//! The class accepts static member function pointers of the owner/parent of
+//! the IO register.
+class IOReg: public BitAccessibleRegister {
+    public:
+        typedef unsigned char(*getter_t)(void*);
+        typedef void (*setter_t)(void*, unsigned char);
+        typedef void (*setter_bit_t)(void*, unsigned char, bool);
+        /*! Creates an IO control register for controlling hardware units
+          \param _p: pointer to object this will be part of
+          \param _g: pointer to get method
+          \param _s: pointer to set method
+          \param _sb: pointer to single-bit set method */
+        explicit IOReg(TraceValueRegister *registry,
+              const std::string &tracename,
+              void *_p,
+              getter_t _g,
+              setter_t _s,
+              setter_bit_t _sb=0)
+            : BitAccessibleRegister(registry, tracename),
+              p(_p), g(_g), s(_s), sb(_sb)
+        {
+            // 'undefined state' doesn't really make sense for IO registers 
+            if (tv)
+                tv->set_written();
+        }
+        
+        /*! Reflects a value change from hardware (for example timer count occured)
+          @param val the new register value */
+        void hardwareChange(unsigned char val) { if(tv) tv->change(val); }
+        /*! Releases the TraceValue to hide this IOReg from registry */
+        void releaseTraceValue(void) {
+            if(tv) {
+                registry->UnregisterTraceValue(tv);
+                delete tv;
+                tv = NULL;
+            }
+        }
+        
+    protected:
+        unsigned char get() const {
+            return g(p);
+        }
+        void set(unsigned char val) {
+            s(p, val);
+        }
+    public:
+        void set_bit(unsigned char bitpos, bool bval) {
+            // use callback if set, otherwise emulate read/modify/write
+            if (sb) {
+                sb(p, bitpos, bval);
+            } else {
+                if (bval)
+                    set(get() | (1 << bitpos));
+                else
+                    set(get() & ~(1 << bitpos));
+            }
+        }
+        
+    private:
+        void *p;
+        getter_t g;
+        setter_t s;
+        setter_bit_t sb;
+};
+
+
 //! A register in IO register space unrelated to any peripheral. "GPIORx" in datasheets.
 /*! Allows clean read and write accesses and simply has one stored byte. */
-class GPIORegister: public RWMemoryMember, public Hardware {
+class GPIORegister: public BitAccessibleRegister, public Hardware {
     
     public:
         GPIORegister(AvrDevice *core,
                      TraceValueRegister *registry,
                      const std::string &tracename):
-            RWMemoryMember(registry, tracename),
+            BitAccessibleRegister(registry, tracename),
             Hardware(core)
     { value = 0; }
         
@@ -111,7 +207,7 @@ class GPIORegister: public RWMemoryMember, public Hardware {
 };
 
 //! Implement CLKPR register
-class CLKPRRegister: public RWMemoryMember, public Hardware {
+class CLKPRRegister: public BitAccessibleRegister, public Hardware {
 
     public:
         CLKPRRegister(AvrDevice *core,
@@ -132,7 +228,7 @@ class CLKPRRegister: public RWMemoryMember, public Hardware {
 };
 
 //! Implement XDIV register
-class XDIVRegister: public RWMemoryMember, public Hardware {
+class XDIVRegister: public BitAccessibleRegister, public Hardware {
 
     public:
         XDIVRegister(AvrDevice *core,
@@ -150,7 +246,7 @@ class XDIVRegister: public RWMemoryMember, public Hardware {
 };
 
 //! Implement OSCCAL register
-class OSCCALRegister: public RWMemoryMember, public Hardware {
+class OSCCALRegister: public BitAccessibleRegister, public Hardware {
 
     public:
         // select type of oscillator, see AVR053 application note!
@@ -197,7 +293,7 @@ class RAM : public RWMemoryMember {
 
 //! Memory on which access should be avoided! :-)
 /*! All accesses to this type of memory will produce an error. */
-class InvalidMem : public RWMemoryMember {
+class InvalidMem : public BitAccessibleRegister {
     private:
         AvrDevice* core;
         int addr;
@@ -212,7 +308,7 @@ class InvalidMem : public RWMemoryMember {
 
 //! An IO register which is not simulated because programmers are lazy.
 /*! Reads and writes are ignored and produce warning. */
-class NotSimulatedRegister : public RWMemoryMember {
+class NotSimulatedRegister : public BitAccessibleRegister {
     private:
         const char * message_on_access;
 
@@ -224,81 +320,6 @@ class NotSimulatedRegister : public RWMemoryMember {
         void set(unsigned char);
 };
 
-//! helper for IOReg static callback wrappers
-#define IOREG_SET_UCHAR(cls, reg)                           \
-    static void Set##reg(void *this_ptr, unsigned char val) \
-    { static_cast<cls*>(this_ptr)->Set##reg(val); }
-
-#define IOREG_GET_UCHAR(cls, reg)                           \
-    static unsigned char Get##reg(void *this_ptr)           \
-    { return static_cast<cls*>(this_ptr)->Get##reg(); }
-
-#define IOREG_SETGET_UCHAR(cls, reg)                        \
-    IOREG_SET_UCHAR(cls, reg)                               \
-    IOREG_GET_UCHAR(cls, reg)
-
-//! IO register to be specialized for a certain class/hardware
-//! The class accepts static member function pointers of the owner/parent of
-//! the IO register.
-class IOReg: public RWMemoryMember {
-    
-    public:
-        typedef unsigned char(*getter_t)(void*);
-        typedef void (*setter_t)(void*, unsigned char);
-        /*! Creates an IO control register for controlling hardware units
-          \param _p: pointer to object this will be part of
-          \param _g: pointer to get method
-          \param _s: pointer to set method */
-        IOReg(TraceValueRegister *registry,
-              const std::string &tracename,
-              void *_p,
-              getter_t _g=0,
-              setter_t _s=0):
-            RWMemoryMember(registry, tracename),
-            p(_p),
-            g(_g),
-            s(_s)
-        {
-            // 'undefined state' doesn't really make sense for IO registers 
-            if (tv)
-                tv->set_written();
-        }
-        
-        /*! Reflects a value change from hardware (for example timer count occured)
-          @param val the new register value */
-        void hardwareChange(unsigned char val) { if(tv) tv->change(val); }
-        /*! Releases the TraceValue to hide this IOReg from registry */
-        void releaseTraceValue(void) {
-            if(tv) {
-                registry->UnregisterTraceValue(tv);
-                delete tv;
-                tv = NULL;
-            }
-        }
-        
-    protected:
-        unsigned char get() const {
-            if (g)
-                return g(p);
-            else if (tv) {
-                avr_warning("Reading of '%s' is not supported.", tv->name().c_str());
-            }
-            return 0;
-        }
-        void set(unsigned char val) {
-            if (s)
-                s(p, val);
-            else if (tv) {
-                avr_warning("Writing of '%s' (with %d) is not supported.", tv->name().c_str(), val);
-            }
-        }
-        
-    private:
-        void *p;
-        getter_t g;
-        setter_t s;
-};
-
 class IOSpecialReg;
 
 //! Interface class to connect hardware units to control registers
@@ -337,7 +358,7 @@ class IOSpecialRegClient {
 //! IO register, which holds configuration for more than one hardware unit
 //! \todo Set method could modify value, how to reflect it on TraceValue mechanism?
 //! Same problem for the get method.
-class IOSpecialReg: public RWMemoryMember {
+class IOSpecialReg: public BitAccessibleRegister {
     
     public:
         //! Creates a IOSpecialReg instance, see RWMemoryMember for more info
diff --git a/src/specialmem.cpp b/src/specialmem.cpp
index b3411eb..66092cf 100644
--- a/src/specialmem.cpp
+++ b/src/specialmem.cpp
@@ -33,7 +33,7 @@ using namespace std;
 RWWriteToFile::RWWriteToFile(TraceValueRegister *registry,
                              const string &tracename,
                              const string &filename):
-    RWMemoryMember(registry, tracename),
+    BitAccessibleRegister(registry, tracename),
     os(filename=="-" ? cout : ofs)
 {
     if(filename != "-")
@@ -53,7 +53,7 @@ unsigned char RWWriteToFile::get() const {
 RWReadFromFile::RWReadFromFile(TraceValueRegister *registry,
                                const string &tracename,
                                const string &filename):
-    RWMemoryMember(registry, tracename),
+    BitAccessibleRegister(registry, tracename),
     is((filename=="-") ? cin : ifs)
 {
     if(filename != "-")
@@ -73,7 +73,7 @@ unsigned char RWReadFromFile::get() const {
 
 RWExit::RWExit(TraceValueRegister *registry,
                const string &tracename) :
-    RWMemoryMember(registry, tracename) {}
+    BitAccessibleRegister(registry, tracename) {}
 
 
 void RWExit::set(unsigned char c) {
@@ -91,7 +91,7 @@ unsigned char RWExit::get() const {
 
 RWAbort::RWAbort(TraceValueRegister *registry,
                  const string &tracename) :
-    RWMemoryMember(registry, tracename) {}
+    BitAccessibleRegister(registry, tracename) {}
 
 void RWAbort::set(unsigned char c) {
     avr_warning("Aborting at simulated program request (write)");
diff --git a/src/specialmem.h b/src/specialmem.h
index 155faa4..e7935a1 100644
--- a/src/specialmem.h
+++ b/src/specialmem.h
@@ -36,7 +36,7 @@
 /*! Memory register which will redirect all write
   accesses to the given (FIFO) file. The output
   format in the file is binary. */
-class RWWriteToFile: public RWMemoryMember {
+class RWWriteToFile: public BitAccessibleRegister {
  public:
     /*! The output filename can be '-' which will
       make this object use cout then. */
@@ -55,7 +55,7 @@ class RWWriteToFile: public RWMemoryMember {
 /*! Memory register which will fulfill all reads with
   a byte drawn from a given (FIFO) file. The input
   format is binary. */
-class RWReadFromFile: public RWMemoryMember {
+class RWReadFromFile: public BitAccessibleRegister {
  public:
     /*! The input filename can be '-' which will
       make this object use cin then. */
@@ -74,7 +74,7 @@ class RWReadFromFile: public RWMemoryMember {
 /*! Any access to this memory will exit the simulator.
   If a byte is written, it will be return code of the simulavr
   process. If a byte is being read, the exit code is 0x00. */
-class RWExit: public RWMemoryMember {
+class RWExit: public BitAccessibleRegister {
  public:
     RWExit(TraceValueRegister *registry, const std::string &tracename="");
  protected:
@@ -84,7 +84,7 @@ class RWExit: public RWMemoryMember {
 
 //! abort() on access memory
 /*! Any access to this memory will instantly stop simulavr. */
-class RWAbort: public RWMemoryMember {
+class RWAbort: public BitAccessibleRegister {
  public:
     RWAbort(TraceValueRegister *registry, const std::string &tracename="");
  protected:
-- 
2.1.4

_______________________________________________
Simulavr-devel mailing list
Simulavr-devel@nongnu.org
https://lists.nongnu.org/mailman/listinfo/simulavr-devel

Reply via email to