PIC14e interrupt bug
Quote from nemka on August 9, 2026, 1:45 pmI may found a bug in PIC14e interrupt handling.
Version: 1.1.0-SR2 rev 260312
When an interrupt is triggered during MOVLB instrucion, the BSR register remains unchanged.
A simple demonstration code is attached.
In main loop, there is another loop that waits for low level on RB4. For this, the program sets RB4 as input and then sets back to output.
Debug signals:
RA2 goes high at the start of main loop and goes low at the end of main loop.
RA0 goes high before MOVLB and goes low after MOVLB.
RA1 is high during interrupt service routine.When Timer2 interrupt is triggered during MOVLB, RB4 remains output and the program stucks in endless loop.
I may found a bug in PIC14e interrupt handling.
Version: 1.1.0-SR2 rev 260312
When an interrupt is triggered during MOVLB instrucion, the BSR register remains unchanged.
A simple demonstration code is attached.
In main loop, there is another loop that waits for low level on RB4. For this, the program sets RB4 as input and then sets back to output.
Debug signals:
RA2 goes high at the start of main loop and goes low at the end of main loop.
RA0 goes high before MOVLB and goes low after MOVLB.
RA1 is high during interrupt service routine.
When Timer2 interrupt is triggered during MOVLB, RB4 remains output and the program stucks in endless loop.
Uploaded files:Quote from arcachofo on August 9, 2026, 4:06 pmHi.
Thanks for reporting but not sure if this is a bug.
As I understand the code it can happen that you select bank 1 at line 123, then an interrupt happens that select bank 0 at line 76.
When the execution returns to line 124, instruction '''bsf TRISB,4''' is executed in bank 0, so executed on PORTB,4 instead of TRISB,4.But not sure about this, let me know if I missunderstood the code.
Hi.
Thanks for reporting but not sure if this is a bug.
As I understand the code it can happen that you select bank 1 at line 123, then an interrupt happens that select bank 0 at line 76.
When the execution returns to line 124, instruction '''bsf TRISB,4''' is executed in bank 0, so executed on PORTB,4 instead of TRISB,4.
But not sure about this, let me know if I missunderstood the code.
Quote from cpexpert6 on August 9, 2026, 7:03 pm# Bug report — Enhanced Mid-Range PIC (Pic14e) core: reading `const` data from program memory (flash) returns garbage## Affected- **Devices:** `p16F1826`, `p16F1827` (core `Pic14e`).- **SimulIDE:** *(fill in your version, e.g. 1.1.0-SR1)*.- **Toolchain that exposes it:** Microchip XC8 (any recent version) compiling for the enhanced mid-range core.## SummaryOn the enhanced mid-range core (`Pic14e`), any firmware that reads a `const` object stored in **program memory / flash** (C string literals, `const` lookup tables, etc.) receives **garbage instead of the real bytes**. The garbage is not random noise — it appears to be SimulIDE's **own internal memory** (the returned bytes spell out fragments of the loaded circuit file), which strongly suggests the CPU model reads from an unimplemented / out-of-range address space and ends up reading host data.The exact same firmware, compiled the same way, runs **correctly on real 16F1826/16F1827 silicon**. The classic mid-range cores in SimulIDE (e.g. `p16F628A`) run the equivalent firmware **correctly**. Only the `Pic14e` core is affected.## Smoking-gun evidenceA minimal firmware that only initializes a 4-bit HD44780 LCD and prints a C string literal:```cLCD_Print("PIC16F1827"); // string literal lives in program memory```displays scrambled characters. Among the scrambled output, the LCD literally shows text such as:```...labPos="-16,2B1" IsBus="false````labPos=` and `IsBus="false"` are **attributes of SimulIDE's own circuit (.sim1) file format**. In other words, the firmware's flash-read returned bytes belonging to SimulIDE's internal data structures, not the program's `const` string. This is the clearest possible indication that the `Pic14e` model is servicing the flash-as-data read from the wrong memory.## Minimal reproduction1. Compile the two tiny programs below with XC8 for `16F1826` (or `16F1827`).2. Wire an HD44780 in 4-bit mode to the MCU (RW to GND). Run each hex.**Program A — reads a `const` string from flash → FAILS (garbage / leaked internal text):**```cstatic void LCD_Print(const char *s){ while(*s) LCD_Data((uint8_t)*s++); }...LCD_Print("PIC16F1827");```**Program B — writes the same text as immediate literals, no flash read → WORKS (clean text):**```cLCD_Data('P'); LCD_Data('I'); LCD_Data('C');LCD_Data('1'); LCD_Data('6'); LCD_Data('F');LCD_Data('1'); LCD_Data('8'); LCD_Data('2'); LCD_Data('7');```Program A fails, Program B succeeds — on the *same* MCU model, *same* wiring, *same* clock. The only difference is whether the data comes from program memory (flash) or from immediate `movlw` values.The same failure hits **any** flash `const`: C string literals, `const uint8_t table[]` accessed by index (e.g. a 7-segment font), the DDRAM row-offset table used by an LCD `SetCursor`, etc.## Likely root causeOn the enhanced mid-range architecture, program memory is visible in the **data address space** (linear/program-memory addressing). XC8 places `const` objects in program flash and reads them by pointing an FSR at the flash address with the high bit set (the `0x8000`-and-up window) and using `moviw` / `INDFn`. It appears the `Pic14e` CPU model in SimulIDE does not implement reads through this program-memory-visibility window, so `moviw`/`INDF` reads that target the `0x8000+` region fall through to an unmapped/host region and return garbage.Suggested things to check in the `Pic14e` core:- FSR/`INDFn` reads where the effective address has bit 15 set (`>= 0x8000`) must be redirected to **program memory** (flash), returning the low byte of the addressed program word.- `moviw`/`movwi` with FSR in the program-memory window.- Confirm classic mid-range works because XC8 uses `retlw` tables there (which the model already handles), while enhanced XC8 uses the linear program-memory read (which it seemingly does not).## Secondary issue found in the shipped data filesWhile investigating, we found a malformed line in the **shipped** `data/PIC/p16F182x/p16F182x_int.xml` (used by both `p16F1826` and `p16F1827`):```xml<interrupt name="T1_OVF" enable="TMR1GIE"flag="TMRGIF" priority="1" vector="0x0004"/>```Three problems on this one line:1. **Missing space** between attributes (`"TMR1GIE"flag`) — not well-formed XML.2. **`flag="TMRGIF"`** references a bit that does not exist; the register file defines **`TMR1GIF`**.3. **Duplicate interrupt name** `T1_OVF` — it is also used (correctly) two lines below for the Timer1 overflow (`enable="TMR1IE" flag="TMR1IF"`), which is the one the peripheral raises.Corrected line (this is the Timer1 *gate* interrupt, so it should have a distinct name):```xml<interrupt name="T1_GATE" enable="TMR1GIE" flag="TMR1GIF" priority="1" vector="0x0004"/>```## ImpactAny enhanced-core firmware that uses string constants or lookup tables (i.e. almost anything with an LCD, a 7-segment font, menu text, etc.) cannot be simulated on `p16F1826`/`p16F1827`, even though it is correct and runs on real hardware.## Workaround on the firmware side (not a fix)Emitting all LCD/7-seg text as immediate character writes and replacing `const` lookup tables with `switch` statements (which compile to `retlw`) avoids the flash read entirely and works in SimulIDE. This is only a workaround for the simulator; the proper fix is to implement program-memory-as-data reads in the `Pic14e` core.
Quote from nemka on August 9, 2026, 7:23 pmHey,
thank you for the quick response.
This family has automatic context saving feature.
From 16F1826 datasheet:
8.5 Automatic Context Saving
Upon entering an interrupt, the return PC address is
saved on the stack. Additionally, the following registers
are automatically saved in the Shadow registers:
• W register
• STATUS register (except for TO and PD)
• BSR register
• FSR registers
• PCLATH register
Upon exiting the Interrupt Service Routine, these registers
are automatically restored. Any modifications to
these registers during the ISR will be lost. If modifications
to any of these registers are desired, the corresponding
Shadow register should be modified and the
value will be restored when exiting the ISR. The
Shadow registers are available in Bank 31 and are
readable and writable. Depending on the user’s application,
other registers may also need to be saved.Before reporting this bug, I checked the SimulIDE source code. pic14einterrupt.cpp contains this feature.
I have not experienced any problems using the W register, even though I also use it in the interrupt service routine in my original code.
I think the W register would also be affected if the problem was the lack of context saving.
Hey,
thank you for the quick response.
This family has automatic context saving feature.
From 16F1826 datasheet:
8.5 Automatic Context Saving
Upon entering an interrupt, the return PC address is
saved on the stack. Additionally, the following registers
are automatically saved in the Shadow registers:
• W register
• STATUS register (except for TO and PD)
• BSR register
• FSR registers
• PCLATH register
Upon exiting the Interrupt Service Routine, these registers
are automatically restored. Any modifications to
these registers during the ISR will be lost. If modifications
to any of these registers are desired, the corresponding
Shadow register should be modified and the
value will be restored when exiting the ISR. The
Shadow registers are available in Bank 31 and are
readable and writable. Depending on the user’s application,
other registers may also need to be saved.
Before reporting this bug, I checked the SimulIDE source code. pic14einterrupt.cpp contains this feature.
I have not experienced any problems using the W register, even though I also use it in the interrupt service routine in my original code.
I think the W register would also be affected if the problem was the lack of context saving.
Quote from arcachofo on August 9, 2026, 9:37 pmBefore reporting this bug, I checked the SimulIDE source code. pic14einterrupt.cpp contains this feature.
Yes, you are right, but there is one missing thing, BSR is restored, but to actually change memory bank it needs to call PicMrCore::setBank( bank ) which is missing.
After adding this call your code does not get into infinite loop as before.So seems that we found the bug.
Before reporting this bug, I checked the SimulIDE source code. pic14einterrupt.cpp contains this feature.
Yes, you are right, but there is one missing thing, BSR is restored, but to actually change memory bank it needs to call PicMrCore::setBank( bank ) which is missing.
After adding this call your code does not get into infinite loop as before.
So seems that we found the bug.
