summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVäinö Kauppila <vaino@vke.fi>2026-04-19 19:02:42 +0300
committerVäinö Kauppila <vaino@vke.fi>2026-04-19 19:02:42 +0300
commit46e1210381106707c2df7f07c56023eb6b760763 (patch)
tree6740a17199f76e2965a511dafcbc7ffb61ceafeb
parenteb8643ddbef43547eb7bfe58789a3908926c7258 (diff)
downloadvhdl_pic-46e1210381106707c2df7f07c56023eb6b760763.tar.gz
vhdl_pic-46e1210381106707c2df7f07c56023eb6b760763.zip
BSRAM progmem. fix Makefile memory command that does not work. add interrupts, bank 1, timer0. Timer0 not tested.
-rw-r--r--Makefile.inner6
-rw-r--r--fw/ds18b20.c156
-rw-r--r--fw/pwm.c53
-rw-r--r--fw/timer0_test.c73
-rw-r--r--hdl/ALU.vhd9
-rw-r--r--hdl/PIC.vhd93
-rw-r--r--hdl/common.vhd2
-rw-r--r--hdl/decoder.vhd7
-rw-r--r--hdl/dpram.vhd128
-rw-r--r--hdl/hdl-prj.json1
-rw-r--r--hdl/onewire.vhd195
-rw-r--r--hdl/pic_top.vhd7
-rw-r--r--hdl/state_machine.vhd32
-rw-r--r--hdl/timer0.vhd131
-rw-r--r--hex2rom.py12
-rw-r--r--primer25k.cst5
16 files changed, 864 insertions, 46 deletions
diff --git a/Makefile.inner b/Makefile.inner
index 957843c..5838b33 100644
--- a/Makefile.inner
+++ b/Makefile.inner
@@ -15,6 +15,8 @@ SYN_FILES = \
$(HDL_DIR)/decoder.vhd \
$(HDL_DIR)/fifo.vhd \
$(HDL_DIR)/uart.vhd \
+ $(HDL_DIR)/onewire.vhd \
+ $(HDL_DIR)/timer0.vhd \
$(HDL_DIR)/ALU.vhd \
$(HDL_DIR)/stack.vhd \
$(HDL_DIR)/dpram.vhd \
@@ -30,6 +32,8 @@ SIM_FILES = \
$(HDL_DIR)/hexfile_reader.vhd \
$(HDL_DIR)/fifo.vhd \
$(HDL_DIR)/uart.vhd \
+ $(HDL_DIR)/onewire.vhd \
+ $(HDL_DIR)/timer0.vhd \
$(HDL_DIR)/ALU.vhd \
$(HDL_DIR)/stack.vhd \
$(HDL_DIR)/dpram.vhd \
@@ -48,7 +52,7 @@ $(BUILD):
# Synthesize
$(BUILD)/$(TOP).json: $(SYN_FILES) | $(BUILD)
ghdl -a $(STD) --workdir=$(BUILD) $(SYN_FILES)
- yosys $(MODULE) -p "ghdl $(STD) --workdir=$(BUILD) $(TOP); memory_map; synth_gowin -json $@ -family gw5a"
+ yosys $(MODULE) -p "ghdl $(STD) --workdir=$(BUILD) $(TOP); synth_gowin -json $@ -family gw5a -nolutram"
# Place and route
$(BUILD)/$(TOP)_pnr.json: $(BUILD)/$(TOP).json $(CST)
diff --git a/fw/ds18b20.c b/fw/ds18b20.c
new file mode 100644
index 0000000..0b57732
--- /dev/null
+++ b/fw/ds18b20.c
@@ -0,0 +1,156 @@
+#include <pic16f84a.h>
+
+typedef unsigned char uint8_t;
+
+__sfr __at(0x70) UART_TX;
+__sfr __at(0x72) UART_STATUS;
+
+__sfr __at(0x73) OW_DATA;
+__sfr __at(0x74) OW_CMD;
+__sfr __at(0x75) OW_STATUS;
+
+#define OW_RESET 0x00
+#define OW_WRITE 0x01
+#define OW_READ 0x02
+
+#define OW_BUSY 0x01
+#define OW_PRESENCE 0x02
+
+#define CMD_SKIP_ROM 0xCC
+#define CMD_CONVERT_T 0x44
+#define CMD_READ_SCRATCH 0xBE
+
+/*
+ * Fractional nibble lookup: each step is 0.0625 deg C.
+ * Tables give the two decimal digits (tenths, hundredths) truncated.
+ * e.g. frac=1 -> 0.0625 -> prints ".06"
+ * frac=8 -> 0.5000 -> prints ".50"
+ */
+static const uint8_t frac_tens[16] = {0,0,1,1,2,3,3,4,5,5,6,6,7,8,8,9};
+static const uint8_t frac_hund[16] = {0,6,2,8,5,1,7,4,0,6,2,8,5,1,7,3};
+
+/*
+ * Timer0 delay. OPTION_REG 0x07: T0CS=0 (Fosc/4), PSA=0, PS=111 (1:256).
+ * At 50 MHz: overflow every 256*256 / (50M/4) = 5.24 ms.
+ * 143 overflows ≈ 750 ms.
+ */
+#define STATUS_RP0 0x20
+#define T0IF 0x04
+
+static void wait_overflows(uint8_t n) {
+ INTCON &= ~T0IF;
+ while (n--) {
+ while (!(INTCON & T0IF));
+ INTCON &= ~T0IF;
+ }
+}
+
+static void delay_750ms(void) { wait_overflows(143); }
+
+static void uart_send(uint8_t b) {
+ while (UART_STATUS & 0x01);
+ UART_TX = b;
+}
+
+static void uart_puts(const char *s) {
+ while (*s) uart_send((uint8_t)*s++);
+}
+
+static void ow_wait(void) {
+ while (OW_STATUS & OW_BUSY);
+}
+
+static void ow_write_byte(uint8_t b) {
+ OW_DATA = b;
+ OW_CMD = OW_WRITE;
+ ow_wait();
+}
+
+static uint8_t ow_read_byte(void) {
+ OW_CMD = OW_READ;
+ ow_wait();
+ return OW_DATA;
+}
+
+static void print_uint8(uint8_t v) {
+ uint8_t started = 0;
+ if (v >= 200) { uart_send('2'); v -= 200; started = 1; }
+ else if (v >= 100) { uart_send('1'); v -= 100; started = 1; }
+ {
+ uint8_t t = '0';
+ while (v >= 10) { v -= 10; t++; }
+ if (started || t != '0') uart_send(t);
+ }
+ uart_send(v + '0');
+}
+
+void main(void) {
+ uint8_t lo, hi, sign, temp_int, frac;
+ uint8_t i;
+
+ /* Configure Timer0: bank 1, OPTION_REG = 0x07 (Fosc/4, prescaler 1:256) */
+ STATUS |= STATUS_RP0;
+ TMR0 = 0x07;
+ STATUS &= ~STATUS_RP0;
+
+ uart_puts("\x1B[2J\x1B[H");
+ uart_puts("DS18B20 Temperature\r\n");
+ uart_puts("-------------------\r\n");
+
+ while (1) {
+ /* Reset */
+ OW_CMD = OW_RESET;
+ ow_wait();
+ if (!(OW_STATUS & OW_PRESENCE)) {
+ uart_puts("No sensor\r\n");
+ delay_750ms();
+ continue;
+ }
+
+ /* Start temperature conversion */
+ ow_write_byte(CMD_SKIP_ROM);
+ ow_write_byte(CMD_CONVERT_T);
+ delay_750ms();
+
+ /* Reset and read scratchpad */
+ OW_CMD = OW_RESET;
+ ow_wait();
+ if (!(OW_STATUS & OW_PRESENCE)) {
+ uart_puts("Lost sensor\r\n");
+ continue;
+ }
+ ow_write_byte(CMD_SKIP_ROM);
+ ow_write_byte(CMD_READ_SCRATCH);
+
+ lo = ow_read_byte(); /* scratchpad byte 0: temp LSB */
+ hi = ow_read_byte(); /* scratchpad byte 1: temp MSB */
+ for (i = 0; i < 7; i++) ow_read_byte(); /* discard remaining bytes */
+
+ /*
+ * DS18B20 12-bit format:
+ * sign = hi bit 7 (and sign-extended through hi[7:4])
+ * integer part = hi[3:0]:lo[7:4]
+ * fractional nibble = lo[3:0] (each bit = 0.0625 deg C)
+ *
+ * Negative values are two's complement of the 16-bit word.
+ */
+ sign = hi & 0x80;
+ if (sign) {
+ lo = ~lo;
+ hi = ~hi;
+ if (lo == 0xFF) { lo = 0; hi++; }
+ else { lo++; }
+ }
+
+ temp_int = ((hi & 0x0F) << 4) | (lo >> 4);
+ frac = lo & 0x0F;
+
+ uart_puts("\rTemp: ");
+ if (sign) uart_send('-');
+ print_uint8(temp_int);
+ uart_send('.');
+ uart_send('0' + frac_tens[frac]);
+ uart_send('0' + frac_hund[frac]);
+ uart_puts(" C\r\n");
+ }
+}
diff --git a/fw/pwm.c b/fw/pwm.c
new file mode 100644
index 0000000..60f8b5c
--- /dev/null
+++ b/fw/pwm.c
@@ -0,0 +1,53 @@
+#include <pic16f84a.h>
+
+typedef unsigned char uint8_t;
+
+/*
+ * Breathing LED via bit-bang PWM on PORTA(0) and PORTB(0).
+ *
+ * The PWM period is 256 counts of the inner counter 'pwm'.
+ * Each duty level is held for HOLD_PERIODS full PWM periods before
+ * stepping, giving a smooth fade up then down (heartbeat pattern).
+ *
+ * At 50 MHz with SDCC's output (~10 instructions/iteration) the inner
+ * loop runs at roughly 5 MHz, so one 256-count PWM period ≈ 51 µs
+ * → ~20 kHz PWM. With HOLD_PERIODS = 32 each brightness step lasts
+ * ~1.6 ms and the full 0→255→0 sweep takes ~820 ms — a natural breath.
+ * Tweak HOLD_PERIODS to taste.
+ */
+
+#define HOLD_PERIODS 32
+
+void main(void) {
+ uint8_t duty = 0; /* current duty cycle, 0..255 */
+ uint8_t dir = 1; /* 1 = ramping up, 0 = ramping down */
+ uint8_t pwm = 0; /* free-running counter within one period */
+ uint8_t periods = 0; /* how many full periods at this duty level */
+
+ while (1) {
+ /* PWM compare: LED on when we're inside the duty window */
+ if (pwm < duty) {
+ PORTA = 0x01;
+ PORTB = 0x01;
+ } else {
+ PORTA = 0x00;
+ PORTB = 0x00;
+ }
+
+ pwm++; /* wraps 255→0, completing one 256-count PWM period */
+
+ if (pwm == 0) {
+ periods++;
+ if (periods == HOLD_PERIODS) {
+ periods = 0;
+ if (dir) {
+ duty++;
+ if (duty == 255) dir = 0; /* peak: start ramping down */
+ } else {
+ duty--;
+ if (duty == 0) dir = 1; /* floor: start ramping up */
+ }
+ }
+ }
+ }
+}
diff --git a/fw/timer0_test.c b/fw/timer0_test.c
new file mode 100644
index 0000000..ef5e622
--- /dev/null
+++ b/fw/timer0_test.c
@@ -0,0 +1,73 @@
+#include <pic16f84a.h>
+
+typedef unsigned char uint8_t;
+
+__sfr __at(0x70) UART_TX;
+__sfr __at(0x72) UART_STATUS;
+
+/*
+ * OPTION_REG 0x07: T0CS=0 (Fosc/4), PSA=0, PS=111 (1:256)
+ * At 50 MHz: overflow every 256*256 / (50M/4) = 5.24 ms → ~191 ovf/sec
+ */
+#define STATUS_RP0 0x20
+#define T0IF 0x04 /* INTCON bit 2 */
+#define T0IE 0x20 /* INTCON bit 5 */
+#define GIE 0x80 /* INTCON bit 7 */
+#define OVF_PER_SEC 191
+
+static volatile uint8_t ovf_count = 0;
+
+void isr(void) __interrupt(0) {
+ if (INTCON & T0IF) {
+ ovf_count++;
+ INTCON &= ~T0IF;
+ }
+}
+
+static void uart_send(uint8_t b) {
+ while (UART_STATUS & 0x01);
+ UART_TX = b;
+}
+
+static void uart_puts(const char *s) {
+ while (*s) uart_send((uint8_t)*s++);
+}
+
+static void print_uint8(uint8_t v) {
+ uint8_t started = 0;
+ if (v >= 200) { uart_send('2'); v -= 200; started = 1; }
+ else if (v >= 100) { uart_send('1'); v -= 100; started = 1; }
+ {
+ uint8_t t = '0';
+ while (v >= 10) { v -= 10; t++; }
+ if (started || t != '0') uart_send(t);
+ }
+ uart_send(v + '0');
+}
+
+void main(void) {
+ uint8_t seconds = 0;
+
+ /* Configure OPTION_REG (bank 1, same 7-bit addr as TMR0) */
+ STATUS |= STATUS_RP0;
+ TMR0 = 0x07;
+ STATUS &= ~STATUS_RP0;
+
+ TMR0 = 0;
+ INTCON = T0IE | GIE; /* enable Timer0 overflow interrupt */
+
+ uart_puts("\x1B[2J\x1B[H");
+ uart_puts("Timer0 test\r\n");
+ uart_puts("-----------\r\n");
+
+ while (1) {
+ ovf_count = 0;
+ while (ovf_count < OVF_PER_SEC); /* ISR bumps ovf_count */
+
+ uart_puts("t = ");
+ print_uint8(seconds);
+ uart_puts(" s\r\n");
+ PORTA ^= 0x01;
+ seconds++;
+ }
+}
diff --git a/hdl/ALU.vhd b/hdl/ALU.vhd
index b7c0344..4d3a065 100644
--- a/hdl/ALU.vhd
+++ b/hdl/ALU.vhd
@@ -206,10 +206,11 @@ begin
- when CALL => null;
- when GOTO => null;
- when RETLW => result <= b;
- when RETUR => null;
+ when CALL => null;
+ when GOTO => null;
+ when RETLW => result <= b;
+ when RETUR => null;
+ when RETFIE => null;
end case;
end process;
diff --git a/hdl/PIC.vhd b/hdl/PIC.vhd
index a31bec7..7471add 100644
--- a/hdl/PIC.vhd
+++ b/hdl/PIC.vhd
@@ -22,7 +22,10 @@ entity PIC is
-- UART physical pins
uart_tx : out std_logic;
- uart_rx : in std_logic
+ uart_rx : in std_logic;
+
+ -- 1-Wire physical pin
+ ow_pin : inout std_logic
);
end entity PIC;
@@ -34,12 +37,13 @@ architecture rtl of PIC is
signal alu_skip : std_logic;
signal mem_q : std_logic_vector(7 downto 0);
- signal mem_qstatus : std_logic_vector(2 downto 0);
+ signal mem_qstatus : std_logic_vector(7 downto 0);
signal mem_we : std_logic;
signal mem_re : std_logic;
signal bit_select : std_logic_vector(2 downto 0);
signal addr : std_logic_vector(6 downto 0);
+ signal mem_addr : std_logic_vector(7 downto 0);
signal sm_data : std_logic_vector(7 downto 0);
signal sm_pc : std_logic_vector(12 downto 0);
signal we_w : std_logic;
@@ -64,6 +68,26 @@ architecture rtl of PIC is
signal uart_rx_empty : std_logic;
signal uart_rx_full : std_logic;
+ -- 1-Wire <-> dpram bridge signals
+ signal ow_tx_byte : std_logic_vector(7 downto 0);
+ signal ow_op : std_logic_vector(1 downto 0);
+ signal ow_start : std_logic;
+ signal ow_rx_byte : std_logic_vector(7 downto 0);
+ signal ow_presence : std_logic;
+ signal ow_busy : std_logic;
+
+ -- Interrupt bridge signals
+ signal irq_pending : std_logic;
+ signal gie_clear : std_logic;
+ signal gie_set : std_logic;
+
+ -- Timer0 bridge signals
+ signal t0if : std_logic;
+ signal tmr0_val : std_logic_vector(7 downto 0);
+ signal tmr0_we_sig : std_logic;
+ signal tmr0_in_sig : std_logic_vector(7 downto 0);
+ signal option_reg : std_logic_vector(7 downto 0);
+
begin
work_reg <= W;
@@ -71,6 +95,9 @@ begin
instruction_return <= instr_ret;
status <= alu_status;
+ -- banking: RP0 (STATUS bit 5) selects bank 0 or bank 1
+ mem_addr <= mem_qstatus(5) & addr;
+
b_operand_select : process(use_literal, sm_data, mem_q)
begin
if use_literal = '1' then
@@ -110,7 +137,10 @@ begin
stack_din => stack_din,
stack_dout => stack_dout,
alu_skip => alu_skip,
- alu_result => alu_result
+ alu_result => alu_result,
+ irq_pending => irq_pending,
+ gie_clear => gie_clear,
+ gie_set => gie_set
);
alu : entity work.alu(rtl)
@@ -119,7 +149,7 @@ begin
b => b_operand,
op => alu_operation,
bit_select => bit_select,
- status_in => mem_qstatus,
+ status_in => mem_qstatus(2 downto 0),
result => alu_result,
status => alu_status,
skip => alu_skip
@@ -136,14 +166,14 @@ begin
);
mem : entity work.dpram(rtl)
- generic map (DEPTH => 128)
+ generic map (DEPTH => 256)
port map (
clk => clk,
we => mem_we,
re => mem_re,
d => alu_result,
d_status => "00000" & alu_status,
- addr => addr,
+ addr => mem_addr,
q => mem_q,
q_status => mem_qstatus,
port_a => port_a,
@@ -155,7 +185,56 @@ begin
uart_rx_byte => uart_rx_byte,
uart_rx_rd_en => uart_rx_rd_en,
uart_rx_empty => uart_rx_empty,
- uart_rx_full => uart_rx_full
+ uart_rx_full => uart_rx_full,
+ ow_tx_byte => ow_tx_byte,
+ ow_op => ow_op,
+ ow_start => ow_start,
+ ow_rx_byte => ow_rx_byte,
+ ow_presence => ow_presence,
+ ow_busy => ow_busy,
+ tmr0_val => tmr0_val,
+ tmr0_we => tmr0_we_sig,
+ tmr0_in => tmr0_in_sig,
+ option_reg => option_reg,
+ t0if_set => t0if,
+ gie_clear => gie_clear,
+ gie_set => gie_set,
+ irq_pending => irq_pending
+ );
+
+ ow_inst : entity work.onewire
+ generic map (
+ CLK_FREQ => CLK_FREQ
+ )
+ port map (
+ clk => clk,
+ reset => reset,
+ ow_pin => ow_pin,
+ start => ow_start,
+ op => ow_op,
+ tx_byte => ow_tx_byte,
+ rx_byte => ow_rx_byte,
+ presence => ow_presence,
+ busy => ow_busy,
+ done => open
+ );
+
+ tmr0_inst : entity work.timer0(rtl)
+ generic map (
+ CLK_FREQ => CLK_FREQ
+ )
+ port map (
+ clk => clk,
+ reset => reset,
+ t0cki => '0',
+ t0cs => option_reg(5),
+ t0se => option_reg(4),
+ psa => option_reg(3),
+ ps => option_reg(2 downto 0),
+ tmr0_we => tmr0_we_sig,
+ tmr0_in => tmr0_in_sig,
+ tmr0_out => tmr0_val,
+ t0if => t0if
);
uart_inst : entity work.uart
diff --git a/hdl/common.vhd b/hdl/common.vhd
index c986ec5..4925f3e 100644
--- a/hdl/common.vhd
+++ b/hdl/common.vhd
@@ -2,7 +2,7 @@ package alu_types is
type alu_op is (ADDWF, ANDWF, ADDLW, ANDLW, BCF, BTFSC,
BSF, BTFSS, CLRF, CLRW, COMF, DECF,
DECFSZ, INCF, INCFSZ, IORLW, MOVF, MOVWF, CALL,
- GOTO, MOVLW, RETLW, RETUR, IORWF, NOP,
+ GOTO, MOVLW, RETLW, RETUR, RETFIE, IORWF, NOP,
RLF, RRF, SUBLW, SUBWF, SWAPF, XORLW, XORWF);
-- Status register bits: Z=2, DC=1, C=0
diff --git a/hdl/decoder.vhd b/hdl/decoder.vhd
index 5a1eb2c..07e785d 100644
--- a/hdl/decoder.vhd
+++ b/hdl/decoder.vhd
@@ -63,8 +63,9 @@ package body decoder is
pure function instruction_get_class(opc : std_logic_vector(13 downto 0))
return instruction_class_t is
begin
- -- RETURN (0x0008) has bits[13:12]="00" but must be treated as LITERAL_OP
+ -- RETURN (0x0008) and RETFIE (0x0009) have bits[13:12]="00"
if opc = "00000000001000" then return LITERAL_OP; end if;
+ if opc = "00000000001001" then return LITERAL_OP; end if;
case opc(13 downto 12) is
-- byte-oriented file-register operations
when "00" =>
@@ -130,8 +131,8 @@ package body decoder is
when LITERAL_OP =>
-- fixed full-word patterns first (fall inside 00xx space)
- -- if opc(11 downto 0) = "000000000001" then return RETFIE;
- if opc(11 downto 0) = "000000001000" then return RETUR;
+ if opc(11 downto 0) = "000000001001" then return RETFIE;
+ elsif opc(11 downto 0) = "000000001000" then return RETUR;
-- elsif opc(11 downto 0) = "000000000011" then return SLEEP;
-- elsif opc(11 downto 0) = "000000000100" then return CLRWDT;
else
diff --git a/hdl/dpram.vhd b/hdl/dpram.vhd
index 44d7adf..92b57f2 100644
--- a/hdl/dpram.vhd
+++ b/hdl/dpram.vhd
@@ -2,23 +2,27 @@ library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--- Data memory with UART memory-mapped registers.
--- Addresses 0x70-0x72 are used (unimplemented in real PIC16F84A, no header conflict):
+-- Data memory with UART and 1-Wire memory-mapped registers.
+-- Addresses 0x70-0x72 are UART (unimplemented in real PIC16F84A, no header conflict):
-- 0x70 UART_TX write: send byte (held until tx_done)
-- 0x71 UART_RX read: current FIFO head (FWFT), pulses rd_en to pop
-- 0x72 UART_STATUS read: "00000" & rx_full & rx_empty & tx_busy
+-- Addresses 0x73-0x75 are 1-Wire:
+-- 0x73 OW_DATA write: tx_byte to send; read: rx_byte received
+-- 0x74 OW_CMD write: bits[1:0]=op (00=reset,01=write,10=read), any write pulses start
+-- 0x75 OW_STATUS read: "000000" & presence & busy
entity dpram is
- generic (DEPTH : positive := 128);
+ generic (DEPTH : positive := 256);
port (
clk : in std_logic;
we : in std_logic;
re : in std_logic;
d : in std_logic_vector(7 downto 0);
d_status : in std_logic_vector(7 downto 0);
- addr : in std_logic_vector(6 downto 0);
+ addr : in std_logic_vector(7 downto 0);
q : out std_logic_vector(7 downto 0);
- q_status : out std_logic_vector(2 downto 0);
+ q_status : out std_logic_vector(7 downto 0);
port_a : out std_logic_vector(7 downto 0);
port_b : out std_logic_vector(7 downto 0);
@@ -31,7 +35,29 @@ entity dpram is
uart_rx_byte : in std_logic_vector(7 downto 0);
uart_rx_rd_en : out std_logic := '0';
uart_rx_empty : in std_logic;
- uart_rx_full : in std_logic
+ uart_rx_full : in std_logic;
+
+ -- 1-Wire interface
+ ow_tx_byte : out std_logic_vector(7 downto 0) := (others => '0');
+ ow_op : out std_logic_vector(1 downto 0) := "00";
+ ow_start : out std_logic := '0';
+ ow_rx_byte : in std_logic_vector(7 downto 0);
+ ow_presence : in std_logic;
+ ow_busy : in std_logic;
+
+ -- Timer0 interface
+ tmr0_val : in std_logic_vector(7 downto 0); -- current TMR0 value
+ tmr0_we : out std_logic := '0'; -- CPU writing TMR0
+ tmr0_in : out std_logic_vector(7 downto 0) := (others => '0');
+
+ -- OPTION_REG (bank 1, 0x81) — read by timer0
+ option_reg : out std_logic_vector(7 downto 0);
+
+ -- Interrupt control (INTCON at 0x0B)
+ t0if_set : in std_logic; -- timer0 overflow: sets INTCON.T0IF (bit 2)
+ gie_clear : in std_logic; -- interrupt ack: clears INTCON.GIE (bit 7)
+ gie_set : in std_logic; -- RETFIE: sets INTCON.GIE (bit 7)
+ irq_pending : out std_logic -- GIE & T0IE & T0IF
);
end entity dpram;
@@ -42,10 +68,13 @@ architecture rtl of dpram is
signal mem : mem_t := (others => (others => '0'));
signal uart_tx_pending : std_logic := '0';
+ signal ow_start_i : std_logic := '0';
+ signal ow_op_i : std_logic_vector(1 downto 0) := "00";
+ signal ow_tx_byte_i : std_logic_vector(7 downto 0) := (others => '0');
begin
- -- STATUS output is combinatorial
- q_status <= mem(16#03#)(2 downto 0);
+ -- STATUS output is combinatorial (full 8-bit register)
+ q_status <= mem(16#03#);
port_a <= mem(16#05#);
port_b <= mem(16#06#);
@@ -53,11 +82,21 @@ begin
-- tx_send is high while a transmission is in progress
uart_tx_send <= uart_tx_pending;
+ ow_start <= ow_start_i;
+ ow_op <= ow_op_i;
+ ow_tx_byte <= ow_tx_byte_i;
+
+ option_reg <= mem(16#81#);
+ irq_pending <= mem(16#0B#)(7) and mem(16#0B#)(5) and mem(16#0B#)(2);
+
+ tmr0_we <= '1' when we = '1' and addr = "00000001" else '0';
+ tmr0_in <= d;
+
-- SR: set when CPU writes to 0x08, clear when UART asserts tx_done
tx_ctrl : process(clk)
begin
if rising_edge(clk) then
- if we = '1' and addr = "1110000" then -- 0x70 UART_TX
+ if we = '1' and addr = "01110000" then -- 0x70 UART_TX
uart_tx_byte <= d;
uart_tx_pending <= '1';
elsif uart_tx_done = '1' then
@@ -66,18 +105,48 @@ begin
end if;
end process tx_ctrl;
+ ow_ctrl : process(clk)
+ begin
+ if rising_edge(clk) then
+ ow_start_i <= '0';
+ if we = '1' then
+ if addr = "01110011" then -- 0x73 OW_DATA
+ ow_tx_byte_i <= d;
+ elsif addr = "01110100" then -- 0x74 OW_CMD
+ ow_op_i <= d(1 downto 0);
+ ow_start_i <= '1';
+ end if;
+ end if;
+ end if;
+ end process ow_ctrl;
+
read : process(clk)
begin
if rising_edge(clk) then
uart_rx_rd_en <= '0'; -- default: no pop
if re = '1' then
- if addr = "1110001" then -- 0x71 UART_RX
- q <= uart_rx_byte; -- FWFT head, valid combinatorially
+ if addr = "00000000" or
+ addr = "10000000" then -- 0x00/0x80 INDF: indirect via FSR
+ q <= mem(to_integer(unsigned(mem(16#04#))));
+ elsif addr = "00000001" then -- 0x01 TMR0 (bank 0, read from timer0)
+ q <= tmr0_val;
+ elsif addr = "00001011" or
+ addr = "10001011" then -- 0x0B/0x8B INTCON (mirrored)
+ q <= mem(16#0B#);
+ elsif addr = "01110001" then -- 0x71 UART_RX
+ q <= uart_rx_byte;
if uart_rx_empty = '0' then
- uart_rx_rd_en <= '1'; -- advance FIFO pointer
+ uart_rx_rd_en <= '1';
end if;
- elsif addr = "1110010" then -- 0x72 UART_STATUS
+ elsif addr = "01110010" then -- 0x72 UART_STATUS
q <= "00000" & uart_rx_full & uart_rx_empty & uart_tx_pending;
+ elsif addr = "01110011" then -- 0x73 OW_DATA
+ q <= ow_rx_byte;
+ elsif addr = "01110101" then -- 0x75 OW_STATUS
+ q <= "000000" & ow_presence & ow_busy;
+ elsif addr = "00000011" or
+ addr = "10000011" then -- 0x03/0x83 STATUS (mirrored)
+ q <= mem(16#03#);
else
q <= mem(to_integer(unsigned(addr)));
end if;
@@ -89,21 +158,40 @@ begin
begin
if rising_edge(clk) then
if we_status = '1' then
- mem(16#03#) <= d_status;
+ -- only Z/DC/C (bits 2:0) come from ALU; preserve RP0, TO, PD etc.
+ mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0);
end if;
if we = '1' then
- if addr = "0000011" then -- 0x03 STATUS: direct write
- mem(16#03#) <= d;
- elsif addr = "1110000" or -- 0x70 UART_TX
- addr = "1110001" or -- 0x71 UART_RX (read-only but handled gracefully)
- addr = "1110010" then -- 0x72 UART_STATUS (read-only)
- -- UART-mapped addresses: don't write to RAM; still update STATUS
+ if addr = "00000000" or
+ addr = "10000000" then -- 0x00/0x80 INDF: indirect via FSR
mem(16#03#) <= d_status;
+ mem(to_integer(unsigned(mem(16#04#)))) <= d;
+ elsif addr = "00000001" then -- 0x01 TMR0: routed to timer0, not RAM
+ mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0);
+ elsif addr = "00001011" or
+ addr = "10001011" then -- 0x0B/0x8B INTCON (mirrored): direct write
+ mem(16#0B#) <= d;
+ elsif addr = "00000011" or
+ addr = "10000011" then -- 0x03/0x83 STATUS (mirrored): direct write
+ mem(16#03#) <= d;
+ elsif addr = "01110000" or -- 0x70 UART_TX
+ addr = "01110001" or -- 0x71 UART_RX (read-only)
+ addr = "01110010" or -- 0x72 UART_STATUS (read-only)
+ addr = "01110011" or -- 0x73 OW_DATA
+ addr = "01110100" or -- 0x74 OW_CMD
+ addr = "01110101" then -- 0x75 OW_STATUS (read-only)
+ -- peripheral-mapped addresses: don't write to RAM
+ mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0);
else
mem(16#03#) <= d_status;
mem(to_integer(unsigned(addr))) <= d;
end if;
end if;
+
+ -- Hardware INTCON modifications (last = highest priority)
+ if t0if_set = '1' then mem(16#0B#)(2) <= '1'; end if;
+ if gie_clear = '1' then mem(16#0B#)(7) <= '0'; end if;
+ if gie_set = '1' then mem(16#0B#)(7) <= '1'; end if;
end if;
end process write;
diff --git a/hdl/hdl-prj.json b/hdl/hdl-prj.json
index b557c2b..7a2e576 100644
--- a/hdl/hdl-prj.json
+++ b/hdl/hdl-prj.json
@@ -14,6 +14,7 @@
{ "file": "ALU.vhd", "language": "vhdl" },
{ "file": "stack.vhd", "language": "vhdl" },
{ "file": "dpram.vhd", "language": "vhdl" },
+ { "file": "onewire.vhd", "language": "vhdl" },
{ "file": "state_machine.vhd", "language": "vhdl" },
{ "file": "PIC.vhd", "language": "vhdl" },
{ "file": "prog_rom.vhd", "language": "vhdl" },
diff --git a/hdl/onewire.vhd b/hdl/onewire.vhd
new file mode 100644
index 0000000..440d34d
--- /dev/null
+++ b/hdl/onewire.vhd
@@ -0,0 +1,195 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.numeric_std.all;
+
+entity onewire is
+ generic (
+ CLK_FREQ : positive := 50_000_000
+ );
+ port (
+ clk : in std_logic;
+ reset : in std_logic;
+
+ ow_pin : inout std_logic; -- 4.7 kohm pull-up to 3.3 V externally
+
+ start : in std_logic;
+ op : in std_logic_vector(1 downto 0); -- "00" reset, "01" write, "10" read
+ tx_byte : in std_logic_vector(7 downto 0);
+ rx_byte : out std_logic_vector(7 downto 0);
+ presence : out std_logic;
+ busy : out std_logic;
+ done : out std_logic
+ );
+end entity onewire;
+
+architecture rtl of onewire is
+
+ constant us : positive := CLK_FREQ / 1_000_000;
+
+ constant RESET_LOW_TICKS : positive := 480 * us;
+ constant PRESENCE_WAIT_TICKS : positive := 15 * us;
+ constant PRESENCE_SAMPLE_END : positive := 75 * us;
+ constant RESET_RECOVER_TICKS : positive := 480 * us;
+ constant WRITE0_LOW_TICKS : positive := 60 * us;
+ constant WRITE1_LOW_TICKS : positive := 6 * us;
+ constant SLOT_TICKS : positive := 70 * us;
+ constant READ_INIT_TICKS : positive := 6 * us;
+ constant READ_SAMPLE_TICKS : positive := 15 * us;
+
+ type state_t is (
+ IDLE,
+ RST_PULL, RST_RELEASE, RST_SAMPLE, RST_RECOVER,
+ WR_PULL, WR_RELEASE,
+ RD_PULL, RD_RELEASE, RD_SAMPLE, RD_RECOVER,
+ BYTE_NEXT
+ );
+
+ signal state : state_t := IDLE;
+ signal ow_drive : std_logic := '0';
+ signal ow_sense : std_logic;
+ signal timer : natural range 0 to RESET_LOW_TICKS + 1 := 0;
+ signal bit_index : natural range 0 to 8 := 0;
+ signal shift_reg : std_logic_vector(7 downto 0) := (others => '0');
+ signal cur_bit : std_logic := '0';
+ signal is_read : std_logic := '0';
+ signal rx_byte_i : std_logic_vector(7 downto 0) := (others => '0');
+
+ signal presence_internal : std_logic := '0';
+begin
+
+ ow_pin <= '0' when ow_drive = '1' else 'Z';
+ ow_sense <= ow_pin;
+
+ presence <= presence_internal;
+
+ rx_byte <= rx_byte_i;
+ busy <= '0' when state = IDLE else '1';
+
+ process(clk)
+ begin
+ if rising_edge(clk) then
+ done <= '0';
+ timer <= timer + 1;
+
+ if reset = '1' then
+ state <= IDLE;
+ ow_drive <= '0';
+ timer <= 0;
+ else
+ case state is
+ when IDLE =>
+ timer <= 0;
+
+ if start = '1' then
+ presence_internal <= '0';
+ case op is
+ -- reset
+ when "00" =>
+ timer <= 0;
+ state <= RST_PULL;
+
+ -- write
+ when "01" =>
+ state <= WR_PULL;
+ shift_reg <= tx_byte;
+ bit_index <= 0;
+ is_read <= '0';
+
+ -- read
+ when "10" =>
+ state <= RD_PULL;
+ bit_index <= 0;
+ is_read <= '1';
+
+ when others => null;
+ end case;
+ end if;
+
+ when RST_PULL =>
+ ow_drive <= '1';
+
+ if timer >= RESET_LOW_TICKS then
+ state <= RST_RELEASE;
+ end if;
+
+
+ when RST_RELEASE =>
+ ow_drive <= '0';
+ timer <= 0;
+ state <= RST_SAMPLE;
+
+ when RST_SAMPLE =>
+ if ow_sense = '0' then
+ presence_internal <= '1';
+ end if;
+
+ if (ow_sense = '1' and presence_internal = '1') or
+ timer >= PRESENCE_SAMPLE_END then
+ state <= RST_RECOVER;
+ end if;
+
+ when RST_RECOVER =>
+ -- wait for 480us since RST_RELEASE
+ if timer >= RESET_RECOVER_TICKS then
+ done <= '1';
+ state <= IDLE;
+ end if;
+
+ when WR_PULL =>
+ ow_drive <= '1';
+ if (shift_reg(bit_index) = '1' and timer >= WRITE1_LOW_TICKS) or
+ (shift_reg(bit_index) = '0' and timer >= WRITE0_LOW_TICKS) then
+ state <= WR_RELEASE;
+ end if;
+
+ when WR_RELEASE =>
+ ow_drive <= '0';
+
+ if timer >= SLOT_TICKS then
+ state <= BYTE_NEXT;
+ end if;
+
+ when RD_PULL =>
+ ow_drive <= '1';
+
+ if timer >= READ_INIT_TICKS then
+ state <= RD_RELEASE;
+ end if;
+
+ when RD_RELEASE =>
+ ow_drive <= '0';
+
+ if timer >= READ_INIT_TICKS + 2 * us then
+ state <= RD_SAMPLE;
+ end if;
+
+ when RD_SAMPLE =>
+ rx_byte_i <= ow_sense & rx_byte_i(7 downto 1);
+ state <= RD_RECOVER;
+
+ when RD_RECOVER =>
+ if timer >= SLOT_TICKS then
+ state <= BYTE_NEXT;
+ end if;
+
+ when BYTE_NEXT =>
+ timer <= 0;
+ if bit_index = 7 then
+ bit_index <= 0;
+ done <= '1';
+ state <= IDLE;
+ else
+ bit_index <= bit_index + 1;
+ if is_read = '1' then
+ state <= RD_PULL;
+ else
+ state <= WR_PULL;
+ end if;
+ end if;
+ when others => state <= IDLE;
+ end case;
+ end if;
+ end if;
+ end process;
+
+end architecture rtl;
diff --git a/hdl/pic_top.vhd b/hdl/pic_top.vhd
index f0cb4bd..f5bd386 100644
--- a/hdl/pic_top.vhd
+++ b/hdl/pic_top.vhd
@@ -14,7 +14,8 @@ entity pic_top is
uart_rx : in std_logic; -- B3
uart_tx : out std_logic; -- C3
activity_led : out std_logic; -- E8 — driven by port_b(0)
- led : out std_logic -- L6 — driven by port_a(0), blink from C
+ led : out std_logic; -- L6 — driven by port_a(0), blink from C
+ ow_pin : inout std_logic -- 1-Wire bus (4.7 kohm pull-up externally)
);
end entity pic_top;
@@ -56,6 +57,7 @@ begin
rom : entity work.prog_rom
port map (
+ clk => clk,
addr => pc,
data => opcode
);
@@ -75,7 +77,8 @@ begin
port_a => port_a,
port_b => port_b,
uart_tx => uart_tx,
- uart_rx => uart_rx
+ uart_rx => uart_rx,
+ ow_pin => ow_pin
);
end architecture rtl;
diff --git a/hdl/state_machine.vhd b/hdl/state_machine.vhd
index 5b1c623..1fb5291 100644
--- a/hdl/state_machine.vhd
+++ b/hdl/state_machine.vhd
@@ -31,7 +31,11 @@ entity state_machine is
stack_dout : in std_logic_vector(12 downto 0);
alu_skip : in std_logic;
- alu_result : in std_logic_vector(7 downto 0)
+ alu_result : in std_logic_vector(7 downto 0);
+
+ irq_pending : in std_logic;
+ gie_clear : out std_logic := '0';
+ gie_set : out std_logic := '0'
);
@@ -67,6 +71,8 @@ begin
instr_ret <= '0';
stack_push <= '0';
stack_pop <= '0';
+ gie_clear <= '0';
+ gie_set <= '0';
if reset = '1' then
pc_internal <= (others => '0');
@@ -78,16 +84,30 @@ begin
case state is
when IFetch =>
+ -- Hardware interrupt: push PC, jump to vector, clear GIE
+ if irq_pending = '1' and not do_phantom_nop then
+ stack_din <= pc_internal;
+ stack_push <= '1';
+ pc_internal <= "0000000000100"; -- 0x0004
+ gie_clear <= '1';
+ instr := instruction_decode((others => '0'));
+ is_phantom := true;
+ is_skip_phantom := false;
+ do_phantom_nop := false;
+ state <= MRead;
+
-- if we had a branch before, this cycle is a NOP.
- if do_phantom_nop then
+ elsif do_phantom_nop then
instr := instruction_decode((others => '0'));
do_phantom_nop := false;
is_phantom := true;
-- is_skip_phantom carries over from whoever set do_phantom_nop
+ state <= MRead;
else
instr := instruction_decode(opcode);
is_phantom := false;
is_skip_phantom := false;
+ state <= MRead;
end if;
-- set use_literal based on decoded instruction, holds through MWrite
@@ -106,7 +126,6 @@ begin
-- for bit-select operations
bit_select <= instr.b;
- state <= Mread;
when MRead =>
@@ -145,13 +164,16 @@ begin
do_phantom_nop := true;
is_skip_phantom := false;
- elsif instr.op = RETUR or instr.op = RETLW then
+ elsif instr.op = RETUR or instr.op = RETLW or instr.op = RETFIE then
-- pop the PC from stack.
pc_internal <= stack_dout;
- stack_pop <= '1'; -- pop deferred to next cycle.
+ stack_pop <= '1';
do_phantom_nop := true;
is_skip_phantom := false;
+ if instr.op = RETFIE then
+ gie_set <= '1';
+ end if;
else
pc_internal <= std_logic_vector(unsigned(pc_internal) + 1);
diff --git a/hdl/timer0.vhd b/hdl/timer0.vhd
new file mode 100644
index 0000000..c2846dc
--- /dev/null
+++ b/hdl/timer0.vhd
@@ -0,0 +1,131 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.numeric_std.all;
+
+entity timer0 is
+ generic (
+ CLK_FREQ : positive := 50_000_000
+ );
+ port (
+ clk : in std_logic;
+ reset : in std_logic;
+
+ -- External clock pin (used when T0CS='1')
+ t0cki : in std_logic;
+
+ -- OPTION_REG control bits
+ t0cs : in std_logic; -- 0=Fosc/4, 1=T0CKI
+ t0se : in std_logic; -- 0=rising edge, 1=falling edge
+ psa : in std_logic; -- 0=prescaler->Timer0, 1=prescaler->WDT
+ ps : in std_logic_vector(2 downto 0); -- prescaler rate select
+
+ -- TMR0 register interface (mapped at 0x01)
+ tmr0_we : in std_logic;
+ tmr0_in : in std_logic_vector(7 downto 0);
+ tmr0_out : out std_logic_vector(7 downto 0);
+
+ -- Overflow flag: pulses one clock on FFh->00h rollover
+ t0if : out std_logic
+ );
+end entity timer0;
+
+architecture rtl of timer0 is
+
+ pure function ps_rate(sel : std_logic_vector(2 downto 0)) return natural is
+ begin
+ case sel is
+ when "000" => return 2;
+ when "001" => return 4;
+ when "010" => return 8;
+ when "011" => return 16;
+ when "100" => return 32;
+ when "101" => return 64;
+ when "110" => return 128;
+ when others => return 256;
+ end case;
+ end function;
+
+ -- Fosc/4 tick generator (one pulse every 4 clocks)
+ constant FOSC4_DIV : positive := 4;
+ signal fosc4_cnt : natural range 0 to FOSC4_DIV - 1 := 0;
+ signal fosc4_tick : std_logic := '0';
+
+ -- 2FF synchroniser for the post-mux clock input
+ signal sync_ff1 : std_logic := '0';
+ signal sync_ff2 : std_logic := '0';
+ signal sync_prev : std_logic := '0'; -- for edge detection after sync
+ signal sync_tick : std_logic := '0'; -- one-cycle tick on selected edge
+
+ -- Prescaler
+ signal prescaler : unsigned(7 downto 0) := (others => '0');
+ signal ps_tick : std_logic := '0'; -- one-cycle pulse out of prescaler
+
+ -- TMR0 counter
+ signal tmr0 : unsigned(7 downto 0) := (others => '0');
+
+ -- Write inhibit: per datasheet, 2 cycles after a TMR0 write the
+ -- prescaler increment is blocked to avoid a spurious count
+ signal wr_inhibit : natural range 0 to 2 := 0;
+
+begin
+
+ fosc4_timer_proc : process(clk)
+ begin
+ if rising_edge(clk) then
+
+ fosc4_tick <= '0';
+
+ if fosc4_cnt = FOSC4_DIV - 1 then
+ fosc4_tick <= '1';
+ fosc4_cnt <= 0;
+ else
+ fosc4_cnt <= fosc4_cnt + 1;
+ end if;
+ end if;
+ end process fosc4_timer_proc;
+
+ prescaler_proc : process(clk)
+ begin
+ if rising_edge(clk) then
+ ps_tick <= '0';
+
+ if tmr0_we = '1' then
+ prescaler <= (others => '0');
+ elsif fosc4_tick = '1' and psa = '0' then
+ if to_integer(prescaler) + 1 >= ps_rate(ps) then
+ prescaler <= (others => '0');
+ ps_tick <= '1';
+ else
+ prescaler <= prescaler + 1;
+ end if;
+ end if;
+ end if;
+ end process prescaler_proc;
+
+ tmr0_out <= std_logic_vector(tmr0);
+
+ timer0_proc : process(clk)
+ begin
+ if rising_edge(clk) then
+ t0if <= '0';
+
+ -- 2FF sync
+ sync_ff1 <= '1' when (psa = '1' and fosc4_tick = '1') or
+ (psa = '0' and ps_tick = '1') else '0';
+ sync_ff2 <= sync_ff1;
+
+ if reset = '1' then
+ tmr0 <= (others => '0');
+ sync_ff1 <= '0';
+ sync_ff2 <= '0';
+ elsif tmr0_we = '1' then
+ tmr0 <= unsigned(tmr0_in);
+ elsif sync_ff2 = '1' then
+ if tmr0 = x"FF" then
+ t0if <= '1';
+ end if;
+ tmr0 <= tmr0 + 1;
+ end if;
+ end if;
+ end process timer0_proc;
+end architecture rtl;
diff --git a/hex2rom.py b/hex2rom.py
index 52610c7..291a702 100644
--- a/hex2rom.py
+++ b/hex2rom.py
@@ -43,6 +43,7 @@ def render(words):
lines.append(f' DEPTH : positive := {DEPTH}')
lines.append(' );')
lines.append(' port (')
+ lines.append(' clk : in std_logic;')
lines.append(' addr : in std_logic_vector(12 downto 0);')
lines.append(' data : out std_logic_vector(13 downto 0)')
lines.append(' );')
@@ -50,14 +51,21 @@ def render(words):
lines.append('')
lines.append('architecture rtl of prog_rom is')
lines.append(f' type rom_t is array (0 to {DEPTH} - 1) of std_logic_vector(13 downto 0);')
- lines.append(' constant rom : rom_t := (')
+ lines.append(' signal rom : rom_t := (')
for i, w in enumerate(words):
bits = format(w, f'014b')
sep = ',' if i < DEPTH - 1 else ' '
lines.append(f' {i:4d} => "{bits}"{sep}')
lines.append(' );')
+ lines.append(' attribute ram_style : string;')
+ lines.append(' attribute ram_style of rom : signal is "block";')
lines.append('begin')
- lines.append(' data <= rom(to_integer(unsigned(addr(9 downto 0))));')
+ lines.append(' process(clk)')
+ lines.append(' begin')
+ lines.append(' if rising_edge(clk) then')
+ lines.append(' data <= rom(to_integer(unsigned(addr(9 downto 0))));')
+ lines.append(' end if;')
+ lines.append(' end process;')
lines.append('end architecture rtl;')
return '\n'.join(lines) + '\n'
diff --git a/primer25k.cst b/primer25k.cst
index 1f3b182..69bc1ff 100644
--- a/primer25k.cst
+++ b/primer25k.cst
@@ -10,7 +10,7 @@ IO_PORT "uart_tx" PULL_MODE=UP IO_TYPE=LVCMOS33;
IO_LOC "uart_rx" B3;
IO_PORT "uart_rx" PULL_MODE=UP IO_TYPE=LVCMOS33;
-IO_LOC "activity_led" E8;
+IO_LOC "activity_led" J5;
IO_PORT "activity_led" PULL_MODE=NONE DRIVE=8 IO_TYPE=LVCMOS33;
IO_LOC "led" L6;
@@ -18,3 +18,6 @@ IO_PORT "led" PULL_MODE=NONE DRIVE=8 IO_TYPE=LVCMOS33;
IO_LOC "reset_btn" H11;
IO_PORT "reset_btn" PULL_MODE=DOWN IO_TYPE=LVCMOS33;
+
+IO_LOC "ow_pin" G10;
+IO_PORT "ow_pin" PULL_MODE=NONE IO_TYPE=LVCMOS33;